몇달전
Eric Evans 와 함께했던 워크샵에서 그는 인터페이스 스타일에 관해 이야기를 꺼냈고, 우리는 그러한 스타일의 인터페이스를 fluent interface 라 부르기로 했다. 비록 일반적인 스타일로 보기는 힘들지만 좀 더 알릴 필요가 있다고 생각된다. 이러한 인터페이스를 설명하는데 가장 좋은 방법은 예를 드는 것이다.
Eric 의 제시한 가장 단순한 예제는
timeAndMoney 라이브러리다. 일반적으로 시간 간격을 만들어내기 위해서 다음과 같은 코드를 흔히 볼 수 있다:
TimePoint fiveOClock, sixOClock;
...
TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);
timeAndMoney 라이브러리 사용자의 경우는 좀 다른 방법으로 이를 수행할 것이다:
TimeInterval meetingTime = fiveOClock.until(sixOClock);
다른 예로 특정 고객의 주문을 생성하는 것을 살펴보겠다. 주문은 다수의 상품 품목(line-items), 구매 제품과 수량 등을 포함한다. 특정 품목은 SKIP 이 가능한데, 이는 그 품목으로 인해서 배송이 지연되는 것을 막기 위해 해당 품목을 빼고도 전체 주문에 대해서는 배송이 이루어질 수 있는 것을 뜻한다. 또한, 주문 처리가 빨리 이뤄지도록 Rush 상태로 설정할 수 있다.
일반적으로 이를 위해서 다음과 같은 코드를 작성할 수 있다:
private void makeNormal(Customer customer) {
Order o1 = new Order();
customer.addOrder(o1);
OrderLine line1 = new OrderLine(6, Product.find("TAL"));
o1.addLine(line1);
OrderLine line2 = new OrderLine(5, Product.find("HPK"));
o1.addLine(line2);
OrderLine line3 = new OrderLine(3, Product.find("LGV"));
o1.addLine(line3);
line2.setSkippable(true);
o1.setRush(true);
}
요점만 설명하면 다수의 객체를 생성하고 이들을 엮어냈다. 만약 생성자에서 모든 설정을 할 수 없다면, 임시 변수를 만든다거나 컬렉션(Collection) 객체에 항목을 담는 것과 같은 일이 요구되었을 것이다.
동일한 일은 Fluent Interface 스타일을 적용해서 처리해보자:
private void makeFluent(Customer customer) {
customer.newOrder()
.with(6, "TAL")
.with(5, "HPK").skippable()
.with(3, "LGV")
.priorityRush();
}
아마 가장 눈에 띄는 점은 내부의
DomainSpecificLanguage 가 수행하는 일들일 것이다. Fluent 라는 용어를 선택한 이유도 여기에 있다. 이러한 유형의 API 는 주로 가독성과 유창한 표현력에 초점을 둔다. 유창한 표현력을 위해서는 많은 사고가 요구되고 API 를 구축하는데 또한 많은 노력이 요구된다. 전자의 예처럼 생성자와 setter 그리고 객체를 추가하는 성격의 메소드와 같은 단순한 API 를 만들어내는 것은 쉽다. 반면에 훌륭한 Fluent API 를 고안하는 것은 상당한 생각을 요구한다.
만약에 Fluent API 의 예를 통해 더 많은 생각을 하고자 한다면,
JMock 을 보라. JMock 역시 다른 Mocking(혹은 Mockup) 라이브러리처럼 복잡한 작동이 가능한 명세가 요구된다. 지난 수년간 많은 Mocking 라이브러리가 있었지만 JMock 은 매우 훌륭한 Fluent API 로 눈에 띈다. 결과값에 대한 기대치를 표현하는 예를 보자:
mock.expects(once()).method("m").with( or(stringContains("hello"),
stringContains("howdy")) );
나는
JAOO2005 에서
Steve Freeman 과
Nat Price 가 발표한 JMock API 의 진화 과정에 대한 훌륭한 발표를 보았고, 그들은 자신들의 경험을 기록으로 정리하겠다고 했지만 이뤄지지 않았다. 제발 누군가 그들이 글을 집필할 시간을 갖게끔 그들의 컴파일러를 좀 쉬게 해주면 좋겠다. :)
지금까지 주로 객체의 환경 설정 과정에서 Fluent API 를 이용하는 사례들을 살펴 보았다. 이런 사례들로 Fluent API 를 특징지을 수 있을지는 의문이지만, 선언적 성격을 띈 상황에서 이들이 주로 쓰여지는데는 무언가 이유가 있을 법하다. Fluent API 를 검증하는 유용한 방법은 Domain Specific Language 의 품질이다.
Fluent API 는 보편적인 방식과는 조금 다른 형태로 API 를 사용하도록 유도한다. 그 중에서도 두드러진 것은 반환값을 갖는 setter 메소드이다. (위의 Order 예를 보면, with 메소드로 order 객체에 order line 객체를 추가하는 경우 order line 객체가 반환된다.) 중괄호를 이용하는 대부분의 언어에서는 대개 수정을 가하는 메소드는 void 이다. 이러한 관습이
명령과 질의 분리(command query separation) 원칙을 따르기 때문 나는 이를 좋아한다. fluent interface 입장에서는 이러한 관습은 적절하지 않기 때문에 이 경우에 대해서는 그러한 관습을 적용하는 것은 적합하지 않다.
연속적으로 원활한 작업(fluent action)을 하기 위해서 return type 을 결정할 수 있다. JMock 은 다음에 필요한 작업이 무엇이냐 근거해서 return type 을 결정하는 중대한 시사점을 제시했다. 이러한 스타일의 두드러진 장점은 IDE 의 마법사 따위를 쓰지 않고 메소드 자동 완성 기능(intellisense)을 통해서 다음에 수행할 코드를 쉽게 도울 수 있다는 점이다. 일반적으로 동적인 언어(Ruby, Python 등)는 간결한 문법을 사용하기 때문에 DSL 용도로 적절하다는 것을 발견했다. 그러나, 메소드 자동 완성 기능을 이용하면 정적인 언어(Java, C 등)에게도 상당한 강점이 생긴다.
fluent interface 를 갖는 메소드가 갖는 문제를 지적하면, 메소드 자체만 놓고 보면 의미가 분명하지 않을 수 있다는 점이다. with 메소드를 API 문서 등을 통해서 순차로 나열된 메소드 가운데서 찾아 보게 된다면 정확한 의미를 파악하기가 쉽지 않다. 그 자체로만 보면 의도를 알 수 없는 모호한 이름을 가진 것이 된다. fluent interface 는 연속적으로 이어지는 행위(fluent action) 속에서 쓰여질 때 강점을 갖는다. 이러한 경우에 그대로 부합하는 예로 빌더 객체(builder objects)를 들 수 있다.
Eric 의 경험에 따르면, fluent interfaces 는 주로 Value objects 의 속성 값을 설정해주는 역할에 주로 쓰인다고 한다. Value objects 는 업무적 관점에서 식별자(domain-meaningful identity)를 갖지 않기에(ID 가 없으면, 단순히 값을 실어 나르는 역할을 함) 쉽게 이들을 생성하거나 제거할 수 있다.
Evans의 도메인 객체 분류(Evans Classification)에 따르면 Order 예에서 Order 객체는 Entity 에 해당하기 때문에 전형적인 예라고 볼 수는 없다.
나는 아직 fluent interfaces 의 다양한 예를 보지 못했기 때문에 우리는 그 장단점을 충분히 알지 못한다고 결론을 내리겠다. 그래서, 이들을 사용하라는 어떠한 권고도 이른 감이 있지만, 아마 fluent interfaces 에 대해 다양한 시도를 해볼만한 시점이라고 생각된다.
원문:
FluentInterface
답글 보기
엠파스 블로그 연재시 답글을 주셨던 내용을 함께 옮겨옵니다.
김형준 2005/12/26 09:20
프로그래밍 할때 좀 더 많은 표현 방법을 사용한다는 것은 프로그램을 처음 배우는 입문자들에게는 프로그램이 지금보다는 훨씬 어렵게 될것이지만, 일정 수준 이상의 프로그래머에게는 표현의 자유를 통해 복잡한 문제를 쉽게 해결할 수 있는 또 다른 방법이지 않을까 생각해봅니다
RE)
영회 2005/12/26 13:27
네.. 동의합니다. 어떤 면에서는 UI 를 구성할 때 초보자의 경우는 마법사처럼 다단계를 거치더라도, GUI 컴포넌트가 적은 화면을 선호하는 반면, 툴에 익숙한 사람은 한번에 처리할 수 있는 UI 를 좋아하는 것과도 일맥하는 것 같더라구요.
yulisys 2006/01/04 18:11
이해하기 쉬운 글처럼 읽을수 있도록 코드를 작성할수 있다는것에 매력이 생기는군요. 하지만, 글 내용에도 있는것 처럼 메소드별로만 보면 전혀 의미파악이 안되는 단점이 있네요.
이를 해결하려면 탄탄한(!) 문서화가 필수일 것 같습니다.
아참, fluent interface 형태의 java api 를 찾아보면 어떤게 있을까요?
지금 바로 생각나는건 StringBuffer 가 생각나는데.. 아닌가
RE)
영회 2006/01/04 18:18
네..범용 환경의 API 보다는 배치(batch)성으로 무언가 설정할 때가 전형적인 예라고 생각됩니다. JDK 에서 찾기는 쉽지 않겠죠. 예를 들어 데이터를 실어 나르는 DTO 성격의 객체에 with() 등을 써서 값을 설정하거나, as() 등을 써서 별칭을 만든다거나.. 이런 것들이 언뜻 떠오르네요.
토비 2006/01/18 13:09
이런 기법은 오래전부터 chained method라는 이름으로 많이 사용되어져온 것으로 알고 있습니다. Hibernate의 Query나 Criteria 인터페이스에서도 아주 유용하게 사용되어지고 있죠.
물론 여기에 대해서 논쟁도 많지만요.
RE)
영회 2006/01/18 16:56
네.. 그렇죠.. 대부분의 기법이나 기술은 이미 오래전부터 내려오던 것이죠.. 더군다나 소프트웨어쪽은 다른 산업에서는 이미 흔한 기법을 대단한 것처럼 적용하는 실정이니까요..
Martin Fowler 의 글의 시사점은 단순히 기법의 내용이 아니라, Interface 설계에 대한 시각(Aspect)를 보여주는 점이었습니다.
Hibernate 는 써보지 않아서 잘 모릅니다만 Query나 Criteria 라면 적절한 적용처가 되겠네요. 좋은 정보 감사합니다. ^^