검색결과 리스트
인터페이스에 해당되는 글 14건
- 2007/03/22 프레임워크의 인터페이스(API) (1)
- 2007/03/21 인터페이스에 대한 다른 선호, 그리고 이를 위한 대응
- 2006/11/04 다양한 인터페이스의 필요 (2)
- 2006/09/19 Humane Interface 와 Minimal Interface 사이의 균형
- 2006/09/19 간결한 인터페이스의 위력
- 2006/09/19 Fluent Interface 적용 사례
- 2006/09/16 Published Interface
- 2006/09/16 암묵적 인터페이스 구현(Implicit Interface Implementation)
- 2006/09/16 인터페이스와 구현 쌍(Interface Implementation Pair)
- 2006/09/16 오리 인터페이스(Duck Interface)
- 2006/09/16 인정있는 인터페이스(Humane Interface) 에피소드
- 2006/09/16 간결함을 지향하는 인터페이스(Minimal Interface)
- 2006/09/13 인터페이스에 대한 생각(학습 비용 vs 생산성)
- 2006/09/13 Fluent Interface (3)
글
프레임워크의 인터페이스(API)
2007/소프트웨어 설계
2007/03/22 11:42
Spring 프레임워크1가 자주 공격받는 것중에 하나는 너무 복잡하다는 것이다. 그런 이야기의 대부분은 정황(Context)을 무시하고 복잡도 자체만을 문제 삼는다. 이는 Spring의 인기를 부정적인 방식으로 이용하려는 시도로 의심되는 논쟁일 뿐이다. 안타깝게도 프레임워크에 대한 이해가 깊지 않은 분들에게 Spring의 API2의 복잡성이 결코 복잡한(?) 것이 아니라는 사실을 설명하기란 무척 어려운 일이다. 메타포를 활용해서 갑자기 설명해보고자 하는 충동에 글을 쓴다.
간결한 인터페이스의 위력에서 예로 들었던 이미지를 보자.

스티브 잡스가 iPod/iMac을 홍보하는 영상에서 iMac 리모컨과 경쟁 제품의 리모컨을 대비한 일이 있다. 사용자 인터페이스에 있어서는 애플이 한가닥한다. 최종 사용자 입장에서는 보편적으로, 어디까지나 보편적으로만 본다면 간결한 것이 최고다.
그러나, 최종 사용자를 위한 것이 아니라면 어떤가? 저러한 기기들의 기판도 단순한 인터페이스를 제공해야 하는가? 리모컨의 기판은 생소하니까 데스크탑의 메인보드를 떠올려보자. USB 포트가 하나라면 간결해서 좋은가? 슬롯은? 각종 포트는? 베이는?

이미지 출처: www.da-view.com/product-sogae/mb885.htm
Spring과 같은 프레임워크가 비슷한 인상을 주는 그림이다. 아래 그림과 대비해서 보면 더욱 그렇다.

확장성이 별로 없는 프레임워크가 무슨 가치가 있는가? 시키는대로 안하면 별로 할 수 있는 것이 없는 프레임워크라면, 프레임워크를 비켜가는 코드를 양산할 가능성이 높다.
Spring이 매력적인 이유는 그럼에도 불구하고 Spring은 충분히 심플하다는 점이다.(물론, 주관적인 척도에 근거한 판단이지만)
간결한 인터페이스의 위력에서 예로 들었던 이미지를 보자.

스티브 잡스가 iPod/iMac을 홍보하는 영상에서 iMac 리모컨과 경쟁 제품의 리모컨을 대비한 일이 있다. 사용자 인터페이스에 있어서는 애플이 한가닥한다. 최종 사용자 입장에서는 보편적으로, 어디까지나 보편적으로만 본다면 간결한 것이 최고다.
그러나, 최종 사용자를 위한 것이 아니라면 어떤가? 저러한 기기들의 기판도 단순한 인터페이스를 제공해야 하는가? 리모컨의 기판은 생소하니까 데스크탑의 메인보드를 떠올려보자. USB 포트가 하나라면 간결해서 좋은가? 슬롯은? 각종 포트는? 베이는?

이미지 출처: www.da-view.com/
Spring과 같은 프레임워크가 비슷한 인상을 주는 그림이다. 아래 그림과 대비해서 보면 더욱 그렇다.

확장성이 별로 없는 프레임워크가 무슨 가치가 있는가? 시키는대로 안하면 별로 할 수 있는 것이 없는 프레임워크라면, 프레임워크를 비켜가는 코드를 양산할 가능성이 높다.
Spring이 매력적인 이유는 그럼에도 불구하고 Spring은 충분히 심플하다는 점이다.(물론, 주관적인 척도에 근거한 판단이지만)
글
인터페이스에 대한 다른 선호, 그리고 이를 위한 대응
2007/소프트웨어 설계
2007/03/21 15:38
댓글로 rukxer님께서 추천해주신 해피해킹키보드, 일명 HHK. 직접 경험해보지는 않았지만, 최소한의 자판으로 최적의 조합을 만들어낸 것으로 짐작된다. 살벌한(?) 가격은 매니아층이 느끼는 만족감(Usability)을 대변해준다.


다기능 키보드와 함께 배열된 HHK1. 선호에 따라서 어떤 것을 사용한다고 해도 전혀 문제될 것이 없다. 전제 조건은 어떤 인터페이스를 사용하더라도 동일한 작동을 보장해줘야 한다는 것이다. 물론, 윈도OS는 이런 것을 완벽하게 보장해준다.
인터넷 사용도가 늘어나면서 사용자 인터페이스의 다양화 현상은 두드러지다. 개인화는 이제 보편적인 현상이고, 위젯의 출현도 같은 맥락으로 볼 수 있다. 한편으로 하드웨어 가격이 낮아질수록 하드웨어 인터페이스의 다양화 현상이 두드러지게되는 것은 불보듯 뻔한 것이다. 앞서 하드웨어와 소프트웨어 인터페이스를 구분없이 혼용해서 이야기했다. 사실 사용자 입장에서는 두가지가 함께 활용될 때 의미를 지닌다.
잠시 개인적인 이야기로 흘러가면 사무실에서는 Dell사의 또각거리는 키보드를 쓰지만, 집에서는 i-rocks의 슬림 키보드를 쓴다. i-rocks의 키감은 너무나 만족스럽다. 재미있는 사실은 누구에게나 i-rocks 키감이 좋은 것은 아니란 점이다. 어떤 이들은 누르는 맛이 있는 키보드를 좋아하고, 어떤 이들은 향수가 느껴지는 기계식 키보드를 좋아한다. 내츄럴 키보드, HHK 팬들도 있지만, 익숙해서 좋다고 고전적인 밝은 회색의 바로 그것(?)을 선호하는 사람도 있다. 이렇듯 인터페이스에 대한 선호라는 것은 꽤나 다양한 법이다.
잠시 입장을 바꿔서 소프트웨어 개발자의 역할에 서보자. 다양한 인터페이스가 요구되는 추세가 요구하는 것은 다양한 요구 형태에 대응할 수 있는 유연한 애플리케이션 개발이다. 화면에 대한 종속성을 포함하는 코드가 최대한 배제된 채로 개발되어야 한다는 점이다. 물론, 현실적으로 화면을 구성하는 코드에 서버에 종속적인 부분이 포함되게 되어 있다. 그 부분을 최소화 하는 것이 유연성을 확보하는데 중요하게 된다. 웹서비스 기술(Web Services)이 이러한 시대적 환경을 토대로 등장한 기술 중에 하나이다.
인터넷 사용도가 늘어나면서 사용자 인터페이스의 다양화 현상은 두드러지다. 개인화는 이제 보편적인 현상이고, 위젯의 출현도 같은 맥락으로 볼 수 있다. 한편으로 하드웨어 가격이 낮아질수록 하드웨어 인터페이스의 다양화 현상이 두드러지게되는 것은 불보듯 뻔한 것이다. 앞서 하드웨어와 소프트웨어 인터페이스를 구분없이 혼용해서 이야기했다. 사실 사용자 입장에서는 두가지가 함께 활용될 때 의미를 지닌다.
잠시 개인적인 이야기로 흘러가면 사무실에서는 Dell사의 또각거리는 키보드를 쓰지만, 집에서는 i-rocks의 슬림 키보드를 쓴다. i-rocks의 키감은 너무나 만족스럽다. 재미있는 사실은 누구에게나 i-rocks 키감이 좋은 것은 아니란 점이다. 어떤 이들은 누르는 맛이 있는 키보드를 좋아하고, 어떤 이들은 향수가 느껴지는 기계식 키보드를 좋아한다. 내츄럴 키보드, HHK 팬들도 있지만, 익숙해서 좋다고 고전적인 밝은 회색의 바로 그것(?)을 선호하는 사람도 있다. 이렇듯 인터페이스에 대한 선호라는 것은 꽤나 다양한 법이다.
잠시 입장을 바꿔서 소프트웨어 개발자의 역할에 서보자. 다양한 인터페이스가 요구되는 추세가 요구하는 것은 다양한 요구 형태에 대응할 수 있는 유연한 애플리케이션 개발이다. 화면에 대한 종속성을 포함하는 코드가 최대한 배제된 채로 개발되어야 한다는 점이다. 물론, 현실적으로 화면을 구성하는 코드에 서버에 종속적인 부분이 포함되게 되어 있다. 그 부분을 최소화 하는 것이 유연성을 확보하는데 중요하게 된다. 웹서비스 기술(Web Services)이 이러한 시대적 환경을 토대로 등장한 기술 중에 하나이다.
- 오래전에 복사한 것이라 이미지 출처가 기억나지 않네요. [본문으로]
글
다양한 인터페이스의 필요
2006/소프트웨어 설계
2006/11/04 23:12
내 블로그 좌측에 달린 구독 버튼
많기도 하고, 들쭉 날쭉 번잡스럽다.
이들 아이콘을 사이즈에 맞게 편집할 재주나 노력할 의사는 없다.
그렇지만, 이들을 찾아서 스킨에 부착하는 일을 했던 것이
지금으로썬 잘한 일 같다.
내 입장에서 두 가지 선택 중에 하나를 택한 것이다.
1) 시각적 인지도를 높이는 icon 활용
2) 텍스트 정렬을 통한 규형미
간단히 Iconic vs 일사분란함
가장 아래의 이메일 구독 박스는 난잡스러움을
가중시키지만 지금 보면 역시 붙여두길 잘했다.
판단의 근거는.. 아래의 피드버너 구독자 퉁계다.
많기도 하고, 들쭉 날쭉 번잡스럽다.
이들 아이콘을 사이즈에 맞게 편집할 재주나 노력할 의사는 없다.
그렇지만, 이들을 찾아서 스킨에 부착하는 일을 했던 것이
지금으로썬 잘한 일 같다.
내 입장에서 두 가지 선택 중에 하나를 택한 것이다.
1) 시각적 인지도를 높이는 icon 활용
2) 텍스트 정렬을 통한 규형미
간단히 Iconic vs 일사분란함
가장 아래의 이메일 구독 박스는 난잡스러움을
가중시키지만 지금 보면 역시 붙여두길 잘했다.
판단의 근거는.. 아래의 피드버너 구독자 퉁계다.
- HanRSS 190
- bloglines 27
- Rojo 6
- FISH 8
- Google 6
- newsgator 1
- Yahoo 1
- email 2
통계가 정확하다면 두 개 정도의 아이콘은 제 구실을 못한 것이지만
나머지는 횟수를 떠나 모두 쓰임을 했다.
블로그 방문자이면서 RSS리더 사용자인 User와 블로그 RSS 제공 시스템을 연결하는
User Interface의 기본적인 역할을 수행한 것이다.
보편적으로 한 사람의 사용자가 복수의 리더를 쓰지 않는다고 가정하면
위의 버튼 하나하나는RSS 구독 기능 관점에서는 별도의 인터페이스가 된다.
글
Humane Interface 와 Minimal Interface 사이의 균형
2006/소프트웨어 설계
2006/09/19 02:36
2005/12/15 (목) 12:32에 엠파스 블로그에 작성한 글에서 일부 발췌합니다.
'대립'이나 '양분'을 염두하고 두 가지 주장을 볼 때 보다는
양자의 균형점이나 각자의 이유를 기반으로 사고하는 것이 더 많은 아이디어를 주었다.
글
간결한 인터페이스의 위력
2006/소프트웨어 설계
2006/09/19 02:19
* 이글은 엠파스 블로그에 2005/12/11 (일) 01:55에 작성했던 내용입니다.
Martin Fowler 의 글, Humane Interface에 대한 반응을 보다가 Elliotte Rusty Harold 의 글을 보게 되었다: http://www.cafeaulait.org/#news2005December7
Martin Fowler 의 글, Humane Interface에 대한 반응을 보다가 Elliotte Rusty Harold 의 글을 보게 되었다: http://www.cafeaulait.org/#news2005December7
그는 동일한 기능을 수행한다면 메소드 숫자를 줄여야 한다는 점을 강조했는데, 예를 든 것이 환상이었다.
Steve Jobs 의 컨퍼런스 과정에서 소개된 내용을 차용한 것이다.
MS 의 미디어센터를 조작하기 위한 두 개의 리모콘 제품과
iMac 의 리모콘이다.
40 여개 vs 6 개의 버튼
와우~
무비를 반쯤 보았다: Steve Jobs
영어로 진행하는 긴 내용이지만... 그의 말처럼 iMac 의 인터페이스는 Fantastic 하다.
현재로써는 충분히 Ultimate Desktop 이라고 할만도 하다.
리모컨이 보여주는 환상적인 인터페이스뿐 아니라
Front Row 라는 기본적인 조작 방식
아기자기한 소프트웨어들의 인터페이스
하드웨어/소프트웨어를 아우르는 인터페이스 원칙이 한결같다.
애플 스타일이라고 하는...
겉으로 드러나는 형상뿐 아니라
감각적으로 느껴지는 부분에 있어서까지..
다시 생각해보면 Fowler 의 예는 정말 잘못된 것으로 볼 수 있는 것 같다.
인터페이스의 메소드 숫자를 줄이는 일이란
글
Fluent Interface 적용 사례
2006/소프트웨어 설계
2006/09/19 02:13
2006/01/11 (수) 23:50 에 작성된 글을 엠파스 블로그에서 옮겨옵니다.
그래서, 이를 래핑하는 과정에서 Fluent Interface를 적용했습니다. 해당 제품의 API는 객체의 데이터를 Dataset 이라는 일종의 컨테이너 클래스에 담아서 전송하게 되어 있었습니다. 이를 자동으로 생성하는 코드를 만도는 것은 적절하지 못한 상황이었습니다. 미봉책이나마 개발자들의 편의성을 높여야 했습니다. 그러다보니 '정형화 된 속성 값을 설정하고, 값을 담는 일' 이기에 Fluent Interface를 적용할만 했던 것이죠.
위와 같은 설정을 보면 속성이 많아지고, 컴포지트(composite) 형태의 객체를 평면화(객체지향 아닌 기술과 데이터를 주고 받을 때 포함하는 객체까지 전부 끌어내는 현상을 제가 부르는 이름..ㅡㅡ;)하면.. 위와 같은 지루한 코드가 상당히 길게 작성됩니다. 실제로는 이런 류의 클래스마다 값을 설정하는데 수 십줄이 필요하게 됩니다.
대개 설정에 관여되는 Setter 유형의 메소드는 반환값이 void 이거나 성공/실패 여부를 반영하기 쉽상인데, 그런 방식이 아니라 생성된 객체를 다시 반환해주면 릴레이로 생성할 수 있죠. 계주를 떠올리면 딱이네요.. ^^
위와 같이 더 축약하실 수도 있죠.
Martin Fowler 의 글, Fluent Interface를 읽고 번역할 즈음에 수행하던 프로젝트에서 라이브러리를 구현하게 되었습니다. 용도가 적절하다 싶어 적용해보고 싶은 마음이 들더군요. 적용 대상으로 선정한 부분은 자바 기반의 웹 프로그램과 특정 라이브러리를 사용하는 제품 사이의 데이터 형식 변환이었습니다. 해당 제품이 제공하는 라이브러리 API 를 써야 하는데 편의성에 약간의 문제가 있었죠.
그래서, 이를 래핑하는 과정에서 Fluent Interface를 적용했습니다. 해당 제품의 API는 객체의 데이터를 Dataset 이라는 일종의 컨테이너 클래스에 담아서 전송하게 되어 있었습니다. 이를 자동으로 생성하는 코드를 만도는 것은 적절하지 못한 상황이었습니다. 미봉책이나마 개발자들의 편의성을 높여야 했습니다. 그러다보니 '정형화 된 속성 값을 설정하고, 값을 담는 일' 이기에 Fluent Interface를 적용할만 했던 것이죠.
Dataset dsCustomer= new Dataset("dsCustomer");
dsCustomer.addColumn("id", ...);
dsCustomer.addColumn("name", ... );
dsCustomer.addColumn("address", ... );
dsCustomer.addColumn("customerTypeId", ... );
dsCustomer.addColumn("customerTypeDescription", ... );
dsCustomer.addColumn("description", ... );
dsCustomer.addColumn("groupId", ... );
dsCustomer.addColumn("groupName", ... );
dsCustomer.addColumn("id", ...);
dsCustomer.addColumn("name", ... );
dsCustomer.addColumn("address", ... );
dsCustomer.addColumn("customerTypeId", ... );
dsCustomer.addColumn("customerTypeDescription", ... );
dsCustomer.addColumn("description", ... );
dsCustomer.addColumn("groupId", ... );
dsCustomer.addColumn("groupName", ... );
위와 같은 설정을 보면 속성이 많아지고, 컴포지트(composite) 형태의 객체를 평면화(객체지향 아닌 기술과 데이터를 주고 받을 때 포함하는 객체까지 전부 끌어내는 현상을 제가 부르는 이름..ㅡㅡ;)하면.. 위와 같은 지루한 코드가 상당히 길게 작성됩니다. 실제로는 이런 류의 클래스마다 값을 설정하는데 수 십줄이 필요하게 됩니다.
Dataset dsCustomer= new Dataset("dsCustomer")
.addColumn("id", ...)
.addColumn("name", ... )
.addColumn("address", ... )
.addColumn("customerTypeId", ... )
.addColumn("customerTypeDescription", ... )
.addColumn("description", ... )
.addColumn("groupId", ... )
.addColumn("groupName", ... );
.addColumn("id", ...)
.addColumn("name", ... )
.addColumn("address", ... )
.addColumn("customerTypeId", ... )
.addColumn("customerTypeDescription", ... )
.addColumn("description", ... )
.addColumn("groupId", ... )
.addColumn("groupName", ... );
대개 설정에 관여되는 Setter 유형의 메소드는 반환값이 void 이거나 성공/실패 여부를 반영하기 쉽상인데, 그런 방식이 아니라 생성된 객체를 다시 반환해주면 릴레이로 생성할 수 있죠. 계주를 떠올리면 딱이네요.. ^^
만일, addColumn 말고도 다른 것들이 많이 설정되야 하는 것이 아니라면 메소드를 더 축약할 수도 있겠죠. Dataset 에는 컬럼만 추가된다거나 하는 상황이라면..
Dataset dsCustomer= new Dataset("dsCustomer")
.with("id", ...).with("name", ... ).with("address", ... )
.with("customerTypeId", ... ).with("customerTypeDescription", ... )
.with("description", ... ).with("groupId", ... ).with("groupName", ... );
.with("id", ...).with("name", ... ).with("address", ... )
.with("customerTypeId", ... ).with("customerTypeDescription", ... )
.with("description", ... ).with("groupId", ... ).with("groupName", ... );
위와 같이 더 축약하실 수도 있죠.
그러나, 이러한 방식이 도움이 되는 경우는 한정되어 있습니다.
- 자동화가 어려운 상황에서 빈번한 설정이 요구되는 작업을 간편하게 하고자 할 때 유용함.
- 설정하는 데이터가 많으면서, 유형은 단순할 수록 효과가 커짐
글
Published Interface
2006/소프트웨어 설계
2006/09/16 23:21
Published Interface 는 Refactoring 에서부터 클래스를 정의한 코드 외부에서 사용하는 인터페이스를 지칭하기 위해 쓴 용어다. 단순히 Java 의 public 이나 C#의 interal 이 아닌 public 의 의미만을 나타내는 것은 아니다.) 이글을 통해서 public 과 published의 public과 private 차이 이상으로 그 차이가 중요함을 주장한 일이 있다.
그 이유는 published interface 가 없다면, 하나의 코드베이스에 인터페이스와 호출하는 코드가 있기 때문에, 변경이 필요한 인터페이스가 있으면 해당 인터페이스를 변경하고, 이를 호출하는 코드를 갱신할 수 있다. 리팩토링 도구를 활용하면 이름을 바꾸는 일은 쉽게 수행할 수 있다. 그러나, published 인터페이스의 경우에는 호출하는 코드를 제어할 수 없기 때문에 보다 많은 것을 고려할 필요가 생긴다.
글
암묵적 인터페이스 구현(Implicit Interface Implementation)
MartinFowler
2006/09/16 22:43
역자 주: 이 글은 두 차례 갱신되었습니다. 인터페이스를 구현한 명시적인 클래스(Implementation)-예를 들어 java 환경에서라면 .class 파일이되겠죠-와 명시적으로 만들지 않아도 이미 전제가 되는 암묵적(implicit) 구현체 사이의 분류는 상당히 현학적으로 들릴 수 있습니다. 왜 이런 쓸데없는 고민을 하느냐고 물으실 분도 있을 것 같습니다. 짐작컨데 테스트를 중시하는 TDD 기반으로 개발을 하다보면, 다수의 인터페이스를 구현하는 클래스를 만들게 되고, 그러다보면 부딪히게 되는 고민일 것 같습니다.
Java 와 C# 모두는 순수한 인터페이스 형식을 고수한다. 만일 Mailable 이라는 인터페이를 선언하면 자바에서
class Customer implements Mailable 라는 문장으로 이를 구현할 수 있다. 하나의 클래스는 몇 개의 인터페이스라도 구현할 수 있다.이러한 방식의 인터페이스 구현은 클래스를 정의할 때 생겨나는 암묵적인 인터페이스(implicit interfaces)를 고려하지 않는다. Customer 클래스에 정의된 모든 public members 는 Customer 의 암묵적 인터페이스가 된다. (내가 본 모은 객체지향언어에서는 이러한 암묵적인 인터페이스를 갖고 있다.)
Java 나 C# 은 암묵적 인터페이스를 구현할 방법을 제공하지 않는다. 즉,
class ValuedCustomer implements Customer 와 같은 것을 허용하지 않는다. 암묵적 인터페이스를 구현한다는 것은 무엇을 의미할까? ValuedCustomer 클래스는 Customer 에 선언된 모든 public 메소드를 구현하지만, 반드시 이들 메소드의 구현체를 갖지는 않겠다는 것이다. 즉, public 메소드의 몸체 부분이나 public 이 아닌 메소드나 데이터를 구현하지 않을 수 있다는 것을 의미한다. 다시 말하면, 인터페이스를 상속하나 구현을 상속하지는 않겟다는 것을 의미한다.어떤 경우에 이러한 기능이 요구될까? 현재와 같은 Collection 프레임워크가 추가되지 이전의Java 초기의 기억을 떠올려보겠다. 우리는 Vector 클래스를 자체적인 구현 클래스로 대치하고 싶었지만, Vector 는 클래스이기 때문에 상속하는 것만이 가능했다. 많은 사람들이 종종 라이브러리가 인터페이스를 제공하지 않아서, 불필요한 기능을 떠안고 상속을 해본 경험이 있을 것이다.
특히 최근에 테스팅 과정에서 이런 일이 비일비재하다. 어떤 클래스의 스텁(stub)을 만들려고 할 때, 인터페이스가 없는 클래스라면 까다로와지거나 아예 불가능한 경우가 생긴다. 테스트 전용으로 인터페이스를 정의해야 하는 일이 발생한다. 인터페이스와 구현 쌍(nterface Implementation Pair) 를 기본 접근 방식으로 활용하지 않는 상황이라면 이런 일은 상당히 꺼려질 것이다. 암묵적인 인터페이스 구현 방식이 보다 간명하게 적용할 수 있을 것이다.
그런데, 프로그래밍 언어가 왜 이들을 지원하지 않을까? 나는 언어 설계자가 아니기에 그 이유를 알 수 없다. Anders Heljsberg(Turbo Pascal, Delphi, C#, 닷넷 프레임워크 등의 설계자)에게 이를 물어볼 기회가 있었고, 명시적으로 멤버를 virtual 로 선언한 경우에 한해서 오버라이딩을 하는 것을 선호한다는 정도로밖에는 답변을 듣지 못했다. 그러나, 이는 주로 상위 클래스의 정의 내용에 변경이 가해지는 하위 클래스의 정의에 관한 것이었고, 상속을 다룬 포괄적인 이야기에 일부였던 관계었다. 게다가 저녁 시간의 짧은 대화였기에 충분한 의미를 부여하기도 애매하다.
이하는 새로 갱신된 내용입니다.
이글을 쓰고 난 후 나의 오랜 동료 Mike Rettig 가 이러한 방식 즉, 하나의 클래스가 암묵적으로 다수의 인터페이스를 구현하는 것에 대해 한가지 문제점을 지적했다. 자바의 예를 들면, Customer 라는 클래스가 있다고 할 때, 이는 암묵적으로 public, protected, package 그리고 private 이라고 하는 네 개의 인터페이스를 갖는다. Customer 객체와 협업하는 객체들은 이 중에 하나의 인터페이스를 쓰게 된다. Customer 객체의 다른 인스턴스는 private feature(프로퍼티나 오퍼레이션)를 쓸 수도 있다. 만일 암묵적인 인터페이스를 구현해야 한다고 가정하면, 이들 네 가지 모두를 구현해거나 그에 상응하는 무언가를 해야 한다. 나는 그것이 얼마나 어려운 작업인지 알 수도 없고, 암묵적으로는public 인터페이스만으로도 충분하다는 사례는 도처에서 발견할 수 있다.
Ian Griffiths 는 클래스와 인터페이스를 섞는 과정에서 발생할 수 있는 문제를 지적했다. Microsoft 의 COM 기술은 실제로는 이들 둘은 명확하게 구분한다: "COM 기반의 객체를 사용하려면 인터페이스를 활용해야 한다. 그래야 항상 고유의 구현 클래스를 보장할 수 있다." VB6 에서는 COM 인터페이스가 개발자가 인식하지 못하는 가운데서 생성되기 때문에 이에 대해 자유로울 수 있다.
동적 타입 기반의 언어(스크립트)에서 이러한 문제가 없다. 다른 클래스의 인터페이스를 구현하고 싶으면, 단지 동일한 메소드를 구현하고, 객체를 생성해서 쓰면 된다. 자바에서도 동적인 프록시(dynamic proxies)를 써서 이를 구현하는 것이 보편화 되어 있지만, 암묵적 인터페이스 구현을 지원하는 것이 보다 효과적인 프로그래밍을 짜게 할 것이다.
이러한 것이 왜 문제가 되는가? 나는 주로 이 문제가 테스트에 관련되어 있다고 생각한다. 만일 새로운 구현 클래스를 사용할 수 없는 상황이라면 테스트 전용 객체(Test Double)를 추가하는데 어려움을 겪을 것이고, 부모클래스가 실제 데이터베이스와의 연결을 필요로 하는 상황이라면 이를 상속한 클래스가 테스트 전용 객체 역할을 구현하는데 쉽지 않을 것이다. 이 문제는 단순히 테스트에 국한된 것일 수 있다 - Robert Conley 의 말에 따르면, 그는 테스트에서만 사용할 것들에 대해서는 VB6 (앞서 언급한 측면) 를 많이 사용한다고 한다.
글
인터페이스와 구현 쌍(Interface Implementation Pair)
2006/소프트웨어 설계
2006/09/16 03:18
모든 클래스를 인터페이스와 쌍일 지어두는 방안. 이렇게 하면, ICustomer 와 Customer 혹은 Customer 와 CustomerImpl가 같은 식으로 쌍을 이루게 할 수 있다. 물론, 인터페이스와 구현 클래스의 경우는 실제로 분리된 타입을 갖고 있기는 하지만, 여러 가지 면에서 이러한 방안은 C/C++ 에서 헤더 파일과 클래스를 나누는 관습과 닮아있다.
이러한 접근의 이점은 이느 시점에서든 모든 클래스를 인터페이스 다른 구현체로 완전하게 대치할 수 있다는 것이다.
그러나, 항상 이점만 갖는 것은 아니다. 복수의 구현 클래스를 갖고 있지 않는 경우라면, IDE의 도움을 받는다고 하더라도 인터페이스와 구현 클래스 사이의 관계를 일일이 유지시키는 것은 부가적인 노력을 요구하다. 게다가 다수의 구현 클래스가 제공되는 상황을 알기가 어렵다.
종종 이러한 상충관계(trade-offs)는 애플리케이션 클래스 작성이냐 라이브러리 작성이냐에 따라 다르게 접근할 수 있다. 애플리케이션 작성의 경우라면 인터페이스가 필요해지는 시점에 Extract Interface 를 수행해서 사용한다. 공개된 라이브러리라면 사용자가 애플리케이션 개발의 경우처럼 빠른 피드백을 줄 수 없기 때문에 라이브러러 클래스의 타입을 인터페이스로 정의하는 것이 더 유용하다. 그러나, Published Interface 를 단순히 구현 클래스의 인터페이스에 맞추서 만들어내는 것은 바람직하다 할 수 없다. 인터페이스는 반드시 클라이언트의 요구에 맞추서 설계되어야 하고, 경우에 따라서는 구현 클래스와 1:1 대응 관계를 유지하지 않을 수도 있다.
글
오리 인터페이스(Duck Interface)
2006/소프트웨어 설계
2006/09/16 03:09
내가 너무 순진했던 것 같다. 그렇지만, Humane Interface 에 그렇게 많은 의견이 따를 줄은 전혀 생각하지 못했다. 아쉽게도 대부분의 의견은 Ruby 의 Array 와 Java 의 List 의 우위 비교를 논하고 있어 원문에서 다루고자 했던 핵심을 벗어났다. 그렇다고 하더라도 연이은 의견이 있었던 점은 바람직한 일이다.
(비록 내가 Ruby 의 Array 나 Ruby 가 더 좋다고 말하려는 의도가 아니었음을 분명히 해야 한다고 느끼지만, 어떠한 상황을 가정하지 않고는 Ruby 와 Java 가운데 어떤 것이 났다고 생각하지도 않는다. Ruby 의 Array 와 Java 의 List 는 전혀 다른 관점으로 설계되었고, 나는 이들을 모두 좋아한다. 핵심은 나는 Java 와 Ruby 는 모두 유용하며 나는 이들을 매우 자주 사용한다는 점이다. 이러한 이유로 이들을 예로 들었던 것이다.)
이들 의견 중 하나는 humane/minimal 접근 관점을 벗어나서 Array 와 List 사이의 차이가 발생하는 다른 이유들이 있음을 제시했다. 그 중 하나는 이들은 유사한 기능을 제공하지만, 두 언어에서 서로 다른 역할을 하고 있다는 점이다.
Ruby 의 Array 는 일부 사람들에게는 혼동을 줄 여지가 있는 메소드를 다수 포함하고 있다. push 와 pop 메소드가 그렇다. Elliotte 이 "누군가 List 에 스택을 집어 넣었다." 라고 표현한 것에서 이러한 혼란을 엿볼 수 있다. Array 에는 또한 shift 와 unshift 메소드를 갖고 있는데 이들은 Elliotte 가 지적한 다음의 말에서 드러나는 적절해 보이지 않는다:
"Queue 나 Stack 을 사용하길 원한다면 List 클래스를 쓰는 것보다는 Queue 나 Stack 클래스를 써야 하지 않는가?"
이들을 읽으면서 잊혀졌던 기억이 떠올랐다. (비록 문법은 우습지만) 객체 지향의 진수를 이해하려면 반드시 읽어야 하는 환상적인 책, Smalltalk Best Practice Patterns
"Smalltlak 에 입문해서 많은 사람들이 가장 처음 작성하는 객체 중 하나가 Stack 이다. Stack 은 기본적인 자료 구조이며, 노래와 이야기에서도 회자되었고,이론적인 프로그래밍 언어에 관한 수많은 논문에서 다뤄졌다. 어떠한 기본 이미지에서도 Stack 클래스가 기본적으로 제공되지는 않는다. 나는 Stack 클래스가 작성되는 것을 여러 차례 보았지만, 이들을 결코 오래 지속되지 않는다." - Kent Beck
Smalltalk 에서 Ruby 의 Array 에 해당하는 것이
OrderedCollection 이지만, 여기에서도 push 나 pop 은 없다. 대신 Kent 는 addLast: 와 removeLast: 를 사용했다.Kent 는 Stack 이나 Queue 의 부족하다고 보지 않았다. - "왜 Smalltalk 에 Stack 이 없느냐? 글쎄. 단지 그건 OrderedCollection 을 이용해서 Stack 을 역할을 하게 하는 문화는 일부다."
여기에 대해서 내 생각이 어떤지는 확실하지 않고, Kent 의 글에서 보이는 불확실성을 분명하게 느낀다. 만일 당신이 Queue 와 같은 것을 사용하기를 원한다면 OrderedCollection new (자바의 new OrderedCollection() 에 해당) 로 객체를 생성하고 addLast 와
removeLast 메소드를 사용하기 보다는 Stack new 와 pop, push 를 쓰는 것이 적절하다고 여길 것이다. 이러한 상황은 정적인 언어와 동적인 언어 사이의 차이점을 포착하게 해준다. 정적인 언어는 엄격한 타입에 근거한 인터페이스를 통해 객체 사이의 메시지를 주고 받지만, 동적 언어의 경우는 이른바 Duck Typing 에 의해 다양한 역할을 수행할 수 있는 클래스를 갖는다. Java 역시 LinkedList 와 같이 Queue 역할을 하는 List 를 갖고 있지만, 대개는 구분되는 인터페이스를 통해서 쓰게 될 것이다. Smalltalk 사용자 입장에서는OrderedCollection 을 통해서 구현할 수 있는데 왜 또 다른 객체를 만들어야 하는지 의문을 갖게 된다. Ruby 사용자의 경우에도 비슷한 반응을 보일 것이고, 그러한 상황에 맞추서 사용할 수 있도록 적절한 이름을 가진 메소드를 추가하려 할 것이다. (사실 Ruby 사용자들이 실제로 Array 가 stack 기능을 수행하는 것에 만족할지 혹은 어쩔 수 없이 남겨진 것으로 여길지는 알 수 없다.)
Charles Miller 가 다음과 같이 말했다. "Java 의 설계는 Small Interface(꼭 필요한 인터페이스) 를 제공하며, Static 메소드 형태로 제공되는 헬퍼 클래스의 유틸리티 함수로 이를 보완해준다. Ruby 의 설계는 유틸리티 메소드가 혼합된 상대적으로 덩치가 큰 클래스를 허용한다."
여기서 얻을 수 있는 결론은 특정 언어의 클래스를 다른 언어에서 통용되는 가치로 평가하는 것을 경계해야 한다는 점이다. Array 가 List 또는 List 와 collection 패키지의 다른 구현 클래스나 인터페이스를 합친 것과 같은가? 아니면 그 보다 더 복잡한 무엇인가? 어떤 이들은 List 클래스에 78 개의 메소드를 추가하는 것을 거부할 것이다. Ruby 의 Array 는 불필요해 보이는 것들을 포함하고 있다고 볼 수 있지만 나는 자바의 collection 으로 작업을 하는 것보다 Array 를 사용하는 것을 더 좋아한다. 그것이 Array 가 Humane interface 지침을 따른데에서 기인한 것인지, Ruby 의 문법의 특성인지는 확실하지 않다.
대체로 어떠한 의견에 동의하는지는 말하기 어렵지만 분명한 것은 다양한 관점을 모두 이해하려고 노력해야 한다는 점이다.
원문: DuckInterface
(이 글에서도 다소 해석하기 어려웠던 부분이나 오히려 빼는 것이 나을 듯한 문구는 제거하였고, 저처럼 Smalltalk 를 잘 모르시는 분을 위해 약간의 주석을 추가하였습니다.)
추가적으로 참조할 만한 글: Java does Duck Typing
글
인정있는 인터페이스(Humane Interface) 에피소드
2006/소프트웨어 설계
2006/09/16 03:06
Martin Fowler 가 Humane Interface 라는 글을 발표하고 난 후 찬반이 갈려 인터넷 상으로 토론 분위기가 조성된 것 같습니다. 그의 bliki 에 링크로 정리된 것들 중 일부만 발췌해서 번역합니다:
Martin Fowler 는 대단히 영리하고 신뢰할만한 사람이다. 그러나, 오늘만큼은 전혀 다른 모습을 보였다: (그의 글 Humane Interface 을 지적하며)
78개 메소드로 구성된 List 클래스는 25개에 비해 좋기는 커녕 세배쯤 나쁜 것이다. List 클래스가 12개의 메소드로 구성되어 있다면 두 배는 좋아질 것이다. 단순함은 사용하는 사람이나 만드는 사람 모두에게 미덕이다. 기본적인 List 클래스에 78개의 메소드가 있을 이유는 전혀 없다. 뿐만 아니라 어떤 클래스도 78개의 공개된(public) 메소드를 갖고 있을 필요가 없다. 하나의 클래스가 78개의 메소드를 외부에 노출한다는 것은 잘못된 코드의 징후로 볼 수 있다. 78 개의 메소드(이하 모두 public 메소드)는 클래스를 익히는데 어려움을 가중 시키고, 사용하고 테스트하는 것은 물론이고 이를 유지해 나가는 것도 힘들게 한다. 클래스가 78개의 메소드를 갖고 있다면 리팩토링을 고려해야 한다.예를 들어, Fowler 가 좋아한다는 Ruby의 List 클래스(실제로는 Array 라고 부르지만 사실상 list 이다)는 List 의 항목중에서 null이 아닌 것의 숫자를 반환하는 메소드를 갖고 있다. 여러분은 그러한 메소드를 필요로 하는가? 내 생각은 그렇지 않다. 물론, 어떤 상황에서 몇몇 사람은 이러한 기능을 필요로 할 수도 있다. 그러나, 누군가 일년에 한번은 필요하다고 해서 모든 기능을 추가할 수는 없다.
또다른 예를 들어 보면, Fowler는 Ruby 의 firts와 last 메소드를 좋아한다고 하지만, list.first() 는 list.get(0) 에 비해 눈에 띄게 단순한 것도 아니다. list.last() 의 경우는 list.get(list.size() - 1) 에 비해 단순하기는 하지만, 이것은 자바가 첨자를 항상 1 이 아닌 0 부터 시작하는 것에서 비롯된 것이다. 그리고, List 의 첫번째 항목을 얻는 것이 얼마나 필요할까? List 의 마지막 항목이 요구되는 것도 그리 자주 쓰이는 기능을 아니다. 일반적으로 List 가 필요한 경우는
Iterator 나 foreach 구문을 통해서 반복을 수행하는 때다. List 가 쓰이는 코드를 보면 대부분 first, last 또는 middle 과 같이 하나의 항목만 쓰는 경우는 많지 않다. Java 의 List 클래스는 Ruby 의 List 에 비해 기능적 부족함이 전혀 없다. 다만 자바의 경우는 다수의 클래스, 특히 Collection 클래스들로 나뉘어져 있고 잘 쓰이지 않는 편의(convenience) 메소드만 생략했을 뿐이다. 그 결과 보다 단순하고 이해하고 쓰기 쉽고 보다 humane interface 의 원칙을 잘 따른 API 가 된 것이다. 그리고 이 글에 대해서 호의적인 평가를 내린 분이 있군요.
부제를 Elliotte vs. Martin: Elliotte wins라고 붙여 놓은 것만 봐도 알 수 있죠. 제목의 kiss는 Keep It Simple and Straightforward 을 의미합니다.
이글은 번역하지는 않겠습니다. 간략히 내용을 요약하면, 아인슈타인의 말을 인용하면 Elliotte 의 손을 들어주네요.
Make it as simple as possible, but not simpler.
최대한 단순하게 하되, 도를 넘지 말라는 의미일텐데요. Ruby 개발자에겐 list.first(), list.last() 가 옳지만, 자바 개발자에겐 list.get(0) 과 get( size()-1 ) 가 적절한 API 라는 것이죠. 프로그래밍 언어 커뮤니티 자체적으로 축적된 컨벤션을 어기는 것은 잘못된 것이란 지적이죠.
두 글은 결국 Martin Fowler 의 Humane Interface 선호 경향을 지적하는 것이 아니라, Ruby 와 Java 를 대상으로 삼아 예를 들었던 점의 오류를 말하려는 것으로 보입니다. 개인적으로는 Fowler의 글을 읽고 자바진영은 Mimimal, Ruby 진영은 Humane과 같은 식으로 어리섞은 단순화로 이해했는데 다른 시각을 주어서 좋네요. Ruby 진영과 자바 진영의 사상에 많은 차이가 있다는 것은 분명한 것 같습니다. 그러나, Fowler 가 든 예는 적절하지 않을 수 있고, Ruby 와 자바 진영의 설계 철학의 차이가 Humane 대 Mimimal 로 극명히 나뉜다고 볼 수는 없을 것 같습니다.
more..
글
간결함을 지향하는 인터페이스(Minimal Interface)
2006/소프트웨어 설계
2006/09/16 02:59
Minimal Interface 는 Humane Interface 에서 언급한 것과 대조되는 API 설계 스타일이다. Minimal Interface 가 표방하는 것은 사용자가 원하는 기능을 모두 제공하는 범주내에서 가장 적은 수의 메소드만 갖도록 API 를 설계하는 것이다. (차이점 비교는Humane Interface 를 참조하라.)
Humane Interface에서 예를 든 Ruby 의 Array 와 자바의 List 를 다시 이용해보자. List 에는 이미 length 메소드가 있고, 색인 기능도 갖고 있어 first 나 last 메소드가 없어도 어렵지 않게 이를 구현할 수 있다. 그 결과, first 와 last 는 편의 함수(convenience methods) 가 된다. Minimal Interface 지지자들이 모든 편의 함수를 기피하는 것은 아니지만, 편의 함수를 쉽게 수용하지는 않는다.
Humane Interface에 비교할만한 Minimal Interface 지지자들의 주장을 들어보자.
인터페이스를 익히는데는 시간이 필요하다. 많은 메소드를 인터페이스로 갖는 클래스를 잘 쓰이지 않을 뿐 아니라 외면 당하기 일쑤다. 적은 수의 집약적인 기능을 갖도록 메소드를 유지해야 사용자들이 클래스를 이해하고 사용하는데 수월하다.
이러한 점은 클래스 설계자에게도 마찬가지다. 클래스 설계 과정에서 빈번히 발생하는 문제는 너무 많은 일을 하는 클래스를 만드는 것이다. 핵심적인 것에 초점을 맞추도록 설계해야 한가지 일을 제대로 할 수 있고, 잘못된 코드가 범람하는 것을 막는데 유리하다.
Humane Interface 지자자의 말대로라면 언제 메소드 추가를 멈출 시점이 언제인가? 누군가 기능을 원할 때마다 이를 추가한다면 메소드는 끝이 없이 늘어날 것이다. 그렇기 때문에 지침이 필요할 것이다. 유용한 것을 제공하라는 그들의 지침은 너무나 모호하다. Minimal Interface 설계 지침은 단순하다. 지금 메소드만으로 사용자들의 작업 수행이 가능하다면 메소드를 추가할 필요가 없다는 것이다.
(무엇이 유용한 것이냐의 이슈는 애플리케이션 코드 작성보다는 공개적으로 배포되는 클래스 라이브러리를 작성할 때 더 의미를 지닌다는 점을 주지하자. 애플리케이션 코드의 경우는 특정한 용도에 국한한다.)
자바나 C# 의 interface 키워드를 사용하는 경우처럼 타입으로 정의한 순수 인터페이스를 사용하는 경우라면 메소드 숫자를 줄여야 할 이유가 또 있다. 인터페이스 구현자의 부담을 줄여야 한다는 점이다. 인터페이스의 메소드 숫자가 많으면 이를 모두 구현해야 하기 때문에 상당한 양의 작업이 요구된다. (추상 클래스를 mixin 으로 활용하면 부담을 줄일 수 있다.)
기능 추가를 원할 때는 다른 클래스를 활용하면 된다. 예를 들어, 자바의 List 에서 정렬을 하기 위해서는 (Ruby 의 Array 는 메소드를 갖고 있지만) Collections 유틸리티 클래스를 사용하면 된다.
라이브러리 작업자의 경우라면, 한번 배포가 된 이후에 메소드를 제거하는 것은 쉽지 않다. 그래서 처음부터 너무 많은 메소드로 시작해서 메소드를 줄이지 못하는 것보다는 부족한 상태로 시작해서 추가해가는 것이 더 났다.
글
인터페이스에 대한 생각(학습 비용 vs 생산성)
2006/소프트웨어 설계
2006/09/13 19:04
jMock과 EasyMock을 비교하다 보니 예전에 번역했던 마틴 파울러의 글이 생각나 찾아 보았다. 인터페이스의 스타일은 Fluent Interface를 좋아하지만, 아직 Mock Object를 사용한 테스트에 익숙하지 않아서인지 프로그래밍 절차가 두드러지게 노출되는 EasyMock의 스타일이 적당해보인다.
Fluent Interface는 DomainSpecificLanguage에 적합하다. 만일, Mock Object를 사용한 테스트라는 도메인 익숙하다면, jMock의 Fluent Interface가 더 끌릴 것이다. 아무 익숙한 개념에 대해 기술하는 경우라면, 프로그래밍 절차가 두드러지게 노출되는 EasyMock의 스타일은 뻔한 내용을 줄을 나눠가면서 길게 작성하는 지루한 일이 될 것이다.
더 극명하게 이해할 수 있는 예를 들어보자. 아래 코드가 EasyMock의 방식이고
mockFoo.bar(arg);
ctrl.setReturnValue(1);
ctrl.replay();
아래가 jMock의 방식이다.
mock.expects(once()).method("bar").with(same(arg)).will(returnValue(1));
가독성(readability) 관점에서 평가는 상반될 수 있는 극명한 차이를 보인다. 마치 초보자도 쉽게 사용할 수 있는 단순하고 직관적인 UI(User Interface)와 한번에 모든 것을 처리할 수 있는 단축키가 풍부한 UI 중에 무엇이 좋으냐를 고르는 것 같은 기분이다. :)
사용자 인터페이스나 프로그래밍 인터페이스나 다음의 상충 관계의 균형을 고려하는 것은 동일하게 적용할 수 있을 것 같다:
낮은 학습 비용 vs 업무(프로그래밍)의 생산성
Fluent Interface는 DomainSpecificLanguage에 적합하다. 만일, Mock Object를 사용한 테스트라는 도메인 익숙하다면, jMock의 Fluent Interface가 더 끌릴 것이다. 아무 익숙한 개념에 대해 기술하는 경우라면, 프로그래밍 절차가 두드러지게 노출되는 EasyMock의 스타일은 뻔한 내용을 줄을 나눠가면서 길게 작성하는 지루한 일이 될 것이다.
더 극명하게 이해할 수 있는 예를 들어보자. 아래 코드가 EasyMock의 방식이고
mockFoo.bar(arg);
ctrl.setReturnValue(1);
ctrl.replay();
아래가 jMock의 방식이다.
mock.expects(once()).method("bar").with(same(arg)).will(returnValue(1));
가독성(readability) 관점에서 평가는 상반될 수 있는 극명한 차이를 보인다. 마치 초보자도 쉽게 사용할 수 있는 단순하고 직관적인 UI(User Interface)와 한번에 모든 것을 처리할 수 있는 단축키가 풍부한 UI 중에 무엇이 좋으냐를 고르는 것 같은 기분이다. :)
사용자 인터페이스나 프로그래밍 인터페이스나 다음의 상충 관계의 균형을 고려하는 것은 동일하게 적용할 수 있을 것 같다:
낮은 학습 비용 vs 업무(프로그래밍)의 생산성
글
Fluent Interface
2006/소프트웨어 설계
2006/09/13 18:40
몇달전 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);
}
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();
}
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")) );
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 에 대해 다양한 시도를 해볼만한 시점이라고 생각된다.




