맨위로가기

플루언트 인터페이스

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

플루언트 인터페이스는 메서드 체이닝을 통해 메서드 호출을 연결하여 가독성을 높이고, 도메인 특화 언어(DSL)와 유사한 인터페이스를 설계하는 프로그래밍 스타일이다. 1970년대 스몰토크에서 메서드 캐스케이딩이 발명된 이후로 존재해왔으며, C++, Java, C#, JavaScript, Scala, Raku, PHP, Python, Swift 등 다양한 프로그래밍 언어에서 구현되었다. 플루언트 인터페이스는 불변 객체와 함께 사용되어 객체의 구성을 만들 수 있지만, 컴파일 타임 오류 검출의 어려움, 디버깅 및 오류 보고의 어려움, 로깅의 어려움, 하위 클래스에서의 메서드 재정의 필요성 등의 문제점도 가지고 있다.

2. 역사

"플루언트 인터페이스"라는 용어는 2005년 말에 처음 사용되었지만, 이 인터페이스 스타일 자체는 1970년대 스몰토크에서 메서드 캐스케이딩이 발명된 이후로 존재해 왔으며, 1980년대에도 수많은 사례가 있었다. 흔한 예시로는 C++의 iostream 라이브러리가 있다.

2. 1. 초기 사례

"플루언트 인터페이스"라는 용어는 2005년 말에 처음 사용되었지만, 이 인터페이스 스타일 자체는 1970년대 스몰토크에서 메서드 캐스케이딩이 발명된 이후로 존재해 왔으며, 1980년대에도 수많은 사례가 있었다. 흔한 예시로는 C++의 iostream 라이브러리가 있는데, 이는 `<<` 또는 `>>` 연산자를 사용하여 메시지를 전달하고, 동일한 객체에 여러 데이터를 전송하며, 다른 메서드 호출을 위한 "조작자"를 허용한다. 다른 초기 예시로는 객체 생성 및 속성 할당에 이 스타일을 사용한 가넷(Garnet) 시스템(1988년 Lisp)과 아뮬렛(Amulet) 시스템(1994년 C++)이 있다.

3. 구현

플루언트 인터페이스는 메서드 체이닝을 사용하여 메서드 캐스케이딩을 구현하며, 각 메서드가 연결된 객체를 반환하도록 한다. 이는 주로 `this` 또는 `self`로 지칭된다.[1]

더 추상적으로, 플루언트 인터페이스는 메서드 체이닝에서 후속 호출의 지침 컨텍스트를 전달하며, 일반적으로 다음과 같다.[1]


  • 호출된 메서드의 반환 값을 통해 정의된다.
  • 자기 참조적이며, 새로운 컨텍스트는 이전 컨텍스트와 동일하다.
  • void 컨텍스트를 반환하여 종료된다.


플루언트 인터페이스는 단순한 메서드 체이닝을 넘어서, "중첩된 함수와 객체 스코핑"과 같은 다른 기술을 사용하여 DSL처럼 읽히는 인터페이스를 설계하는 것을 포함한다.[1]

3. 1. 핵심 원리

플루언트 인터페이스는 (기본적으로 캐스케이딩을 지원하지 않는 언어의 경우) 일반적으로 메서드 체이닝을 사용하여 메서드 캐스케이딩을 구현하며, 각 메서드가 연결된 객체를 반환하도록 하여 구현된다. 이는 종종 `this` 또는 `self`로 지칭된다.[1] 더 추상적으로 말하면, 플루언트 인터페이스는 메서드 체이닝에서 후속 호출의 지침 컨텍스트를 전달하며, 일반적으로 컨텍스트는 다음과 같다.[1]

  • 호출된 메서드의 반환 값을 통해 정의됨
  • 자기 참조적이며, 새로운 컨텍스트는 마지막 컨텍스트와 동일함
  • void 컨텍스트의 반환을 통해 종료됨


"플루언트 인터페이스"는 단순한 메서드 체이닝을 통한 캐스케이딩 그 이상을 의미하며, "중첩된 함수와 객체 스코핑"과 같은 다른 기술을 사용하여 DSL처럼 읽히는 인터페이스를 설계하는 것을 포함한다.[1]

3. 2. 다양한 언어에서의 구현

플루언트 인터페이스는 메서드 체이닝을 사용하여 구현되며, 각 메서드가 연결된 객체를 반환하도록 한다. 이는 `this` 또는 `self`를 통해 이루어진다. 플루언트 인터페이스는 단순한 메서드 체이닝을 넘어, 중첩된 함수와 객체 스코핑과 같은 기술을 사용하여 DSL처럼 읽히는 인터페이스를 설계하는 것을 포함한다.[1]

다양한 프로그래밍 언어에서 플루언트 인터페이스가 구현되는 방식은 다음과 같다.

  • C#: LINQ에서 플루언트 프로그래밍을 광범위하게 사용하며, 확장 메서드를 기반으로 구현된다. .NET 테스팅 프레임워크인 NUnit에서도 활용된다.
  • C++: 표준 iostream에서 연산자 오버로딩을 연결하는 방식으로 사용된다.
  • Java: jMock, jOOQ, EasyMock 등의 라이브러리에서 사용된다. Java Swing API의 GridBagLayout 클래스에서도 활용된다.
  • JavaScript: jQuery와 같은 라이브러리에서 널리 사용되며, 주로 데이터베이스 쿼리 구현에 활용된다.
  • 스칼라: 트레이트와 `with` 키워드를 사용하여 메서드 호출과 클래스 믹스인에 유창한 구문을 지원한다.
  • Raku: 속성을 읽기/쓰기로 선언하고 `given` 키워드를 사용하는 방식으로 구현할 수 있다.
  • PHP: `$this`를 사용하여 현재 객체를 반환하는 방식으로 구현된다.
  • 파이썬: 인스턴스 메서드에서 `self`를 반환하여 구현할 수 있지만, 귀도 반 로섬은 이 방법을 권장하지 않는다.[3]
  • Swift: 함수에서 `self`를 반환하는 방식으로 구현된다.[1]

3. 2. 1. C#

C#은 "표준 쿼리 연산자"를 사용하여 쿼리를 구축하기 위해 LINQ에서 플루언트 프로그래밍을 광범위하게 사용한다. 구현은 확장 메서드를 기반으로 한다.

플루언트 인터페이스는 동일한 객체를 작동/공유하는 일련의 메서드를 연결하는 데에도 사용할 수 있다.

.NET 테스팅 프레임워크인 NUnit은 C#의 메서드와 속성을 플루언트 스타일로 혼합하여 "제약 조건 기반" 어설션을 구성한다.

3. 2. 2. C++

C++에서 플루언트 인터페이스의 일반적인 사용 예는 표준 iostream이며, 이는 오버로딩된 연산자를 연결한다.

다음은 C++에서 더 전통적인 인터페이스 위에 플루언트 인터페이스 래퍼를 제공하는 예이다.

```cpp

// 기본 정의

class GlutApp {

private:

int w_, h_, x_, y_, argc_, display_mode_;

char **argv_;

char *title_;

public:

GlutApp(int argc, char** argv) {

argc_ = argc;

argv_ = argv;

}

void setDisplayMode(int mode) {

display_mode_ = mode;

}

int getDisplayMode() {

return display_mode_;

}

void setWindowSize(int w, int h) {

w_ = w;

h_ = h;

}

void setWindowPosition(int x, int y) {

x_ = x;

y_ = y;

}

void setTitle(const char *title) {

title_ = title;

}

void create(){;}

};

// 기본 사용법

int main(int argc, char **argv) {

GlutApp app(argc, argv);

app.setDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_ALPHA|GLUT_DEPTH); // 프레임 버퍼 설정

app.setWindowSize(500, 500); // 윈도우 설정

app.setWindowPosition(200, 200);

app.setTitle("My OpenGL/GLUT App");

app.create();

}

// 플루언트 래퍼

class FluentGlutApp : private GlutApp {

public:

FluentGlutApp(int argc, char **argv) : GlutApp(argc, argv) {} // 부모 생성자 상속

FluentGlutApp &withDoubleBuffer() {

setDisplayMode(getDisplayMode() | GLUT_DOUBLE);

return *this;

}

FluentGlutApp &withRGBA() {

setDisplayMode(getDisplayMode() | GLUT_RGBA);

return *this;

}

FluentGlutApp &withAlpha() {

setDisplayMode(getDisplayMode() | GLUT_ALPHA);

return *this;

}

FluentGlutApp &withDepth() {

setDisplayMode(getDisplayMode() | GLUT_DEPTH);

return *this;

}

FluentGlutApp &across(int w, int h) {

setWindowSize(w, h);

return *this;

}

FluentGlutApp &at(int x, int y) {

setWindowPosition(x, y);

return *this;

}

FluentGlutApp &named(const char *title) {

setTitle(title);

return *this;

}

// create() 이후에는 체이닝이 의미 없으므로, *this를 반환하지 않음

void create() {

GlutApp::create();

}

};

// 플루언트 사용법

int main(int argc, char **argv) {

FluentGlutApp(argc, argv)

.withDoubleBuffer().withRGBA().withAlpha().withDepth()

.at(200, 200).across(500, 500)

.named("My OpenGL/GLUT App")

.create();

}

3. 2. 3. Java

jMock 테스팅 프레임워크에서 플루언트 테스트 기대치의 예는 다음과 같다.[1]

```Java

mock.expects(once()).method("m").with( or(stringContains("hello"),

stringContains("howdy")) );

```

jOOQ 라이브러리는 Java에서 SQL을 플루언트 API로 모델링한다.

```java

Author author = AUTHOR.as("author");

create.selectFrom(author)

.where(exists(selectOne()

.from(BOOK)

.where(BOOK.STATUS.eq(BOOK_STATUS.SOLD_OUT))

.and(BOOK.AUTHOR_ID.eq(author.ID))));

```

fluflu 주석 프로세서는 Java 주석을 사용하여 플루언트 API를 생성할 수 있다.

JaQue 라이브러리는 Java 8 람다를 런타임에 표현 트리 형식의 객체로 나타낼 수 있게 하여 타입 안전한 플루언트 인터페이스를 만들 수 있게 한다. 즉, 다음과 같이 작성하는 대신:

```java

Customer obj = ...

obj.property("name").eq("John")

```

다음과 같이 작성할 수 있다.

```java

method(customer -> customer.getName() == "John")

```

또한, 모의 객체 테스팅 라이브러리인 EasyMock은 표현력 있는 프로그래밍 인터페이스를 제공하기 위해 이 스타일의 인터페이스를 광범위하게 사용한다.

```java

Collection mockCollection = EasyMock.createMock(Collection.class);

EasyMock

.expect(mockCollection.remove(null))

.andThrow(new NullPointerException())

.atLeastOnce();

```

Java Swing API에서 LayoutManager 인터페이스는 Container 객체가 어떻게 Component 배치를 제어할 수 있는지 정의한다. 더 강력한 `LayoutManager` 구현 중 하나는 레이아웃 제어가 어떻게 발생하는지 지정하기 위해 `GridBagConstraints` 클래스를 사용해야 하는 GridBagLayout 클래스이다. 이 클래스 사용의 전형적인 예는 다음과 같다.

```java

GridBagLayout gl = new GridBagLayout();

JPanel p = new JPanel();

p.setLayout( gl );

JLabel l = new JLabel("Name:");

JTextField nm = new JTextField(10);

GridBagConstraints gc = new GridBagConstraints();

gc.gridx = 0;

gc.gridy = 0;

gc.fill = GridBagConstraints.NONE;

p.add( l, gc );

gc.gridx = 1;

gc.fill = GridBagConstraints.HORIZONTAL;

gc.weightx = 1;

p.add( nm, gc );

```

이것은 많은 코드를 생성하고 여기서 정확히 무엇이 일어나고 있는지 보기 어렵게 만든다. `Packer` 클래스는 플루언트 메커니즘을 제공하므로 다음과 같이 작성할 수 있다:[2]

```java

JPanel p = new JPanel();

Packer pk = new Packer( p );

JLabel l = new JLabel("Name:");

JTextField nm = new JTextField(10);

pk.pack( l ).gridx(0).gridy(0);

pk.pack( nm ).gridx(1).gridy(0).fillx();

```

플루언트 API가 소프트웨어를 작성하는 방식을 단순화하고 메서드의 반환 값이 항상 해당 컨텍스트에서 추가 작업을 위한 컨텍스트를 제공하기 때문에 사용자가 API에 훨씬 더 생산적이고 편안하게 느끼도록 돕는 API 언어를 만드는 많은 경우가 있다.

3. 2. 4. JavaScript

jQuery는 플루언트 인터페이스 방식을 사용하는 대표적인 자바스크립트 라이브러리이다. 플루언트 빌더는 주로 "데이터베이스 쿼리" 구현에 사용되는데, Dynamite 클라이언트 라이브러리에서 사용되는 예시는 다음과 같다.[1]

```javascript

// 테이블에서 항목 가져오기

client.getItem('user-table')

.setHashKey('userId', 'userA')

.setRangeKey('column', '@')

.execute()

.then(function(data) {

// data.result: 결과 객체

})

```

자바스크립트에서 플루언트 인터페이스를 구현하는 간단한 방법은 프로토타입 상속과 `this`를 이용하는 것이다.[1]

```javascript

// 예시 https://schier.co/blog/2013/11/14/method-chaining-in-javascript.html

class Kitten {

constructor() {

this.name = 'Garfield';

this.color = 'orange';

}

setName(name) {

this.name = name;

return this;

}

setColor(color) {

this.color = color;

return this;

}

save() {

console.log(

`saving ${this.name}, the ${this.color} kitten`

);

return this;

}

}

// 사용법

new Kitten()

.setName('Salem')

.setColor('black')

.save();

3. 2. 5. Scala

스칼라는 트레이트와 `with` 키워드를 사용하여 메서드 호출과 클래스 믹스인 모두에 유창한 구문을 지원한다. 예를 들면 다음과 같다.

```scala

class Color { def rgb(): Tuple3[Decimal] }

object Black extends Color { override def rgb(): Tuple3[Decimal] = ("0", "0", "0"); }

trait GUIWindow {

// 유창한 그리기를 위해 this를 반환하는 렌더링 메서드

def set_pen_color(color: Color): this.type

def move_to(pos: Position): this.type

def line_to(pos: Position, end_pos: Position): this.type

def render(): this.type = this // 아무것도 그리지 않고 this를 반환하여 자식 구현이 유창하게 사용하도록 함

def top_left(): Position

def bottom_left(): Position

def top_right(): Position

def bottom_right(): Position

}

trait WindowBorder extends GUIWindow {

def render(): GUIWindow = {

super.render()

.move_to(top_left())

.set_pen_color(Black)

.line_to(top_right())

.line_to(bottom_right())

.line_to(bottom_left())

.line_to(top_left())

}

}

class SwingWindow extends GUIWindow { ... }

val appWin = new SwingWindow() with WindowBorder

appWin.render()

3. 2. 6. Raku

Raku에서 플루언트 인터페이스를 구현하는 가장 간단한 방법 중 하나는 속성을 읽기/쓰기로 선언하고 `given` 키워드를 사용하는 것이다. 타입 주석은 선택 사항이지만, 네이티브 점진적 타이핑은 공용 속성에 직접 쓰는 것을 훨씬 안전하게 만든다.

```perl6

class Employee {

subset Salary of Real where * > 0;

subset NonEmptyString of Str where * ~~ /\S/; # 공백 문자가 아닌 문자가 최소한 하나 이상

has NonEmptyString $.name is rw;

has NonEmptyString $.surname is rw;

has Salary $.salary is rw;

method gist {

return qq:to[END];

Name: $.name

Surname: $.surname

Salary: $.salary

END

}

}

my $employee = Employee.new();

given $employee {

.name = 'Sally';

.surname = 'Ride';

.salary = 200;

}

say $employee;

# 출력:

# Name: Sally

# Surname: Ride

# Salary: 200

3. 2. 7. PHP

PHP에서, 인스턴스를 나타내는 특수 변수 `$this`를 사용하여 현재 객체를 반환할 수 있다. 따라서 `return $this;`는 메서드가 인스턴스를 반환하도록 한다. 아래 예제는 `Employee` 클래스와 이름, 성 및 급여를 설정하는 세 가지 메서드를 정의한다. 각 메서드는 `Employee` 클래스의 인스턴스를 반환하여 메서드 체이닝을 허용한다.




final class Employee

{

private string $name;

private string $surname;

private string $salary;

public function setName(string $name): self

{

$this->name = $name;

return $this;

}

public function setSurname(string $surname): self

{

$this->surname = $surname;

return $this;

}

public function setSalary(string $salary): self

{

$this->salary = $salary;

return $this;

}

public function __toString(): string

{

return <<
Name: {$this->name}

Surname: {$this->surname}

Salary: {$this->salary}

INFO;

}

}

# Employee 클래스의 새 인스턴스를 생성한다. Tom Smith, 급여 100:

$employee = (new Employee())

  • >setName('Tom')
  • >setSurname('Smith')
  • >setSalary('100');


# Employee 인스턴스의 값을 표시한다:

echo $employee;

# 표시:

# Name: Tom

# Surname: Smith

# Salary: 100


3. 2. 8. Python

파이썬에서 인스턴스 메서드의 `self`를 반환하여 플루언트 패턴을 구현할 수 있다.

그러나 파이썬 창시자인 귀도 반 로섬은 이 방법을 권장하지 않으며[3], 새로운 값을 반환하지 않는 연산에는 파이썬답지 않다고(관용적이지 않다고) 간주한다. 반 로섬이 제시하는 플루언트 패턴의 적절한 예시는 문자열 처리 연산이다.

```python

class Poem:

def __init__(self, title: str) -> None:

self.title = title

def indent(self, spaces: int):

"""Indent the poem with the specified number of spaces."""

self.title = " " * spaces + self.title

return self

def suffix(self, author: str):

"""Suffix the poem with the author name."""

self.title = f"{self.title} - {author}"

return self

```

```pycon

>>> Poem("Road Not Travelled").indent(4).suffix("Robert Frost").title

' Road Not Travelled - Robert Frost'

3. 2. 9. Swift

Swift 3.0 이상에서 함수에서 `self`를 반환하는 것은 플루언트 패턴을 구현하는 한 가지 방법이다.[1]

```swift

class Person {

var firstname: String = ""

var lastname: String = ""

var favoriteQuote: String = ""

@discardableResult

func set(firstname: String) -> Self {

self.firstname = firstname

return self

}

@discardableResult

func set(lastname: String) -> Self {

self.lastname = lastname

return self

}

@discardableResult

func set(favoriteQuote: String) -> Self {

self.favoriteQuote = favoriteQuote

return self

}

}

```

```swift

let person = Person()

.set(firstname: "John")

.set(lastname: "Doe")

.set(favoriteQuote: "I like turtles")

4. 불변성 (Immutability)

불변 플루언트 인터페이스는 카피 온 라이트 의미론을 활용하여 만들 수 있다. 이 패턴의 변형에서는 내부 속성을 수정하고 동일한 객체에 대한 참조를 반환하는 대신, 객체를 복제하고 복제된 객체에서 속성을 변경한 후 해당 객체를 반환한다.

이러한 접근 방식은 인터페이스를 사용하여 특정 지점에서 분기될 수 있는 객체의 구성을 만들 수 있다는 장점이 있다. 이를 통해 둘 이상의 객체가 일정량의 상태를 공유하고 서로 간섭하지 않으면서 더 사용할 수 있다.

5. 문제점

플루언트 인터페이스는 코드 가독성을 높일 수 있지만, 다음과 같은 문제점도 가지고 있다.


  • 컴파일 타임 오류 검출의 어려움: 타입 언어를 사용하는 경우, 생성자에 모든 매개변수가 필요하면 컴파일 시간에 오류를 확인할 수 있다. 그러나 플루언트 인터페이스는 런타임에만 오류를 확인할 수 있어 컴파일러의 타입 안전성 검사를 놓치게 된다. 이는 페일 패스트 방식과도 모순된다.
  • 디버깅 및 오류 보고의 어려움: 한 줄로 연결된 코드는 디버거에서 중단점을 설정하기 어렵고, 단계별 실행도 불편하다. 예외 발생 시 어떤 메서드 호출에서 문제가 발생했는지 파악하기 어려울 수 있다.
  • 로깅의 어려움: 플루언트 호출 체인 중간에 로깅을 추가하려면 호출을 끊어야 해서 번거롭다.
  • 하위 클래스에서의 메서드 재정의 필요성: C++, Java, C# 등 강력한 형식화된 언어에서는 하위 클래스가 플루언트 인터페이스를 사용하려면 상위 클래스의 모든 메서드를 재정의해야 할 때가 많다.


이러한 문제점들은 플루언트 인터페이스의 사용성을 떨어뜨리고, 코드 유지보수를 어렵게 만들 수 있다.

5. 1. 컴파일 타임 오류 검출의 어려움

타입 언어에서 모든 매개변수를 필요로 하는 생성자를 사용하면 컴파일 시간에 오류가 발생하지만, 플루언트 접근 방식은 런타임 오류만 생성할 수 있어 최신 컴파일러의 모든 타입 안전성 검사를 놓치게 된다. 또한 오류 보호를 위한 페일 패스트 접근 방식과도 모순된다.

5. 2. 디버깅 및 오류 보고의 어려움

단일 행으로 연결된 구문은 디버거가 체인 내에 중단점을 설정할 수 없어 디버깅하기가 더 어려울 수 있다. 디버거에서 단일 행 구문을 단계별로 실행하는 것도 덜 편리할 수 있다.

```java

java.nio.ByteBuffer.allocate(10).rewind().limit(100);

```

또 다른 문제는 특히 동일한 메서드를 여러 번 호출하는 경우 예외를 발생시킨 메서드 호출이 무엇인지 명확하지 않을 수 있다는 것이다. 이러한 문제는 구문을 여러 줄로 나누어 해결할 수 있으며, 체인 내에 중단점을 설정하고 코드를 줄 단위로 쉽게 단계별로 실행할 수 있도록 하면서 가독성을 유지한다.

```java

java.nio.ByteBuffer

.allocate(10)

.rewind()

.limit(100);

```

그러나 일부 디버거는 예외가 어떤 행에서 발생했든 상관없이 항상 예외 백트레이스에 첫 번째 행을 표시한다.

5. 3. 로깅의 어려움

플루언트 호출 체인 중간에 로깅을 추가하는 것은 문제가 될 수 있다. 예를 들어 다음과 같은 코드를 보자.

```java

ByteBuffer buffer = ByteBuffer.allocate(10).rewind().limit(100);

```

`rewind()` 메서드 호출 후 `buffer`의 상태를 로깅하려면, 플루언트 호출을 끊어야 한다.

```java

ByteBuffer buffer = ByteBuffer.allocate(10).rewind();

log.debug("rewind 후 첫 번째 바이트는 " + buffer.get(0));

buffer.limit(100);

```

이는 확장 메서드를 지원하는 언어에서 원하는 로깅 기능을 감싸는 새로운 확장을 정의하여 해결할 수 있다. 예를 들어, C#에서는 다음과 같이 할 수 있다.

```csharp

static class ByteBufferExtensions

{

public static ByteBuffer Log(this ByteBuffer buffer, Log log, Action getMessage)

{

string message = getMessage(buffer);

log.debug(message);

return buffer;

}

}

// 사용법:

ByteBuffer

.Allocate(10)

.Rewind()

.Log( log, b => "rewind 후 첫 번째 바이트는 " + b.Get(0) )

.Limit(100);

5. 4. 하위 클래스에서의 메서드 재정의 필요성

강력한 형식화된 언어(C++, Java, C# 등)의 하위 클래스는 반환 유형을 변경하기 위해 플루언트 인터페이스에 참여하는 상위 클래스의 모든 메서드를 재정의해야 하는 경우가 많다. 예를 들면 다음과 같다.

```java

class A {

public A doThis() { ... }

}

class B extends A {

public B doThis() { super.doThis(); return this; } // 반환 유형을 B로 변경해야 합니다.

public B doThat() { ... }

}

...

A a = new B().doThat().doThis(); // A.doThis()를 재정의하지 않아도 작동합니다.

B b = new B().doThis().doThat(); // A.doThis()가 재정의되지 않으면 실패합니다.

```

F-바운드 다형성을 표현할 수 있는 언어는 이러한 어려움을 피하기 위해 사용할 수 있다. 예를 들면 다음과 같다.

```java

abstract class AbstractA> {

@SuppressWarnings("unchecked")

public T doThis() { ...; return (T)this; }

}

class A extends AbstractA {}

class B extends AbstractA {

public B doThat() { ...; return this; }

}

...

B b = new B().doThis().doThat(); // 작동합니다!

A a = new A().doThis(); // 작동합니다.

```

부모 클래스의 인스턴스를 생성할 수 있으려면 두 개의 클래스, 즉 `AbstractA`와 `A`로 분리해야 했다. 후자는 내용이 없다(필요한 경우 생성자만 포함). 하위-하위 클래스(등)도 원하는 경우 이 접근 방식을 쉽게 확장할 수 있다.

```java

abstract class AbstractB> extends AbstractA {

@SuppressWarnings("unchecked")

public T doThat() { ...; return (T)this; }

}

class B extends AbstractB {}

abstract class AbstractC> extends AbstractB {

@SuppressWarnings("unchecked")

public T foo() { ...; return (T)this; }

}

class C extends AbstractC {}

...

C c = new C().doThis().doThat().foo(); // 작동합니다!

B b = new B().doThis().doThat(); // 여전히 작동합니다.

```

Scala와 같은 종속적으로 형식화된 언어에서는 메서드를 항상 `this`를 반환하도록 명시적으로 정의할 수도 있으므로, 하위 클래스가 플루언트 인터페이스를 활용할 수 있도록 한 번만 정의할 수 있다.

```scala

class A {

def doThis(): this.type = { ... } // this를 반환하고 항상 this를 반환합니다.

}

class B extends A {

// 재정의가 필요하지 않습니다!

def doThat(): this.type = { ... }

}

...

val a: A = new B().doThat().doThis(); // 체이닝은 양방향으로 작동합니다.

val b: B = new B().doThis().doThat(); // 그리고 두 메서드 체인 모두 B를 반환합니다!

참조

[1] 문서 FluentInterface http://www.martinfow[...] 2005-12-20
[2] 웹사이트 Interface Pack200.Packer https://docs.oracle.[...] 2019-11-13
[3] 웹사이트 '[Python-Dev] sort() return value' https://mail.python.[...] 2003-10-17
[4] 문서 FluentInterface http://www.martinfow[...] 2005-12-20



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com