데코레이터 패턴
디자인패턴 2012. 11. 2. 15:033. 데코레이터 패턴
1. 스타버즈에 오신것을 환영 합니다.
1.1 처음 매장 오픈
- 스타버즈 커피는 엄청난 급속도로 성장한 초대형 커피 전문점으로 유명하다.
- 스타버즈 커피샵은 워낙 빠르게 생장했기 때문에 다양한 음료를 모두 포괄하는 주문시스템을 이제 갖추려고 한다.
- 처음 사업을 시작할 무렵에 만들어진 클래스는 아래와 같다.
|
- 커피를 주문할 때 스팀 우유나 두유, 모카를 추가하고, 그 위에 휘핑 크림을 얹기도 해야 한다면..
|
1.2 첫 번째 리팩토링
- 이거 정말 황당하네, 왜 이렇게 클래스가 많이 필요한 거죠?
그냥 인스턴스 변수하고 슈퍼클래스 상속을 써서 추가 사항을 관리하면 안될까요?
|
- 어떤 문제가 있을까요?
- 첨가물 가격이 바뀔 때마다 기존 코드를 수정해야 한다.
- 첨가물의 종류가 많아지면 새로운 메소드를 추가해야 한다.
- 새로운 음료가 출시될 수도 있습니다. 그 중에는 특정 첨가물이 들어가면 안 되는 경우도 있을 겁니다.
아이스 티를 생각해 보면 Tea서브 클래스에서도 hasWhip()같은 메소드를 여전히 상속 받을 것이다. - 손님이 더블 모카를 주문하면 어떻게 해야 할까요?
(참고1) OCP( Open-Closed Principle)
- OCP( Open-Closed Principle)는 가장 중요한 디자인 원칙 가운에 하나다.
- 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.
- 즉 기존 코드는 건드리지 않은 채로 확장을 통해서 새로운 행동을 간단하게 추가할 수 있도록 하면,
새로운 기능을 유연하게 추가할 수 있어, 주변 환경에 잘 적응할 수 있으면서도 강하고 튼튼한 디자인을 만들 수 있다. - 추가 설명 : http://www.zdnet.co.kr/builder/dev/modeling/0,39031637,39134727,00.htm
1.3 데코레이터 패턴 적용
- 이번에 사용 할 방법은 특정 음료에서 시작해서 첨가물로 그 음료를 장식(decorate) 할 것이다.
- 예를 들어 모카와 휘핑크림을 추가한 다크로스트 커피를 주문 한다면 아래와 같이 할 수 있을 것이다.
- ① DarkRoast 객체를 가져온다.
- ② Mocha 객체로 장식한다.
- ③ Whip 객체로 장식한다.
- ④ cost() 메소드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해당 객체들에게 위임한다.
① DarkRoast 객체에서 시작 합니다. | ② 손님이 Mocha를 주문했으니 Mocha객체를 만들고 그 객체로 DarkRoast를 감쌉니다. | ③ 손님이 휘핑크림도 같이 주문했기 때문에 Whip 데코레이터를 만들고 그 ?체로 Mocha를 감쌉니다. |
|
|
|
④ 이제 가격을 계산해 볼까요? 가장 바깥쪽에 있는 데코레이터 Whip의 cost()를 호출하면 된다.
|
1.3.1 Beverage 클래스를 장식해 봅시다.
|
사무실에서 들은 이야기.. |
---|
|
1.3.2 코드를 만들어 봅시다.
Beverage.java public abstract class Beverage { protected String description = "제목없음"; public abstract double cost(); public String getDescription() { return description; } } | CondimentDecorator.java public abstract class CondimentDecorator extends Beverage { //모든 첨가물 데코레이터에서 getDescription() 메소드를 새로 구현하도록 만들 계획임 public abstract String getDescription(); } |
Espresso.java //에스프레소 커피 public class Espresso extends Beverage { public Espresso(){ //Beverage로부터 상속받음 description = "에스프레소 커피"; } @Override public double cost() { return 1.99; } } | Mocha.java //Mocha는 데코레이터기 때문에 CondimentDecorator를 확장 합니다. public class Mocha extends CondimentDecorator { //감싸고자 하는 음료(하우스블렌드,다크로스트,디카페인,에스프레소)를 저장하는 인스턴스. Beverage beverage; //생성자를 이용해서 감싸고자 하는 음료 객체를 전달한다. public Mocha(Beverage beverage){ this.beverage = beverage; } @Override public String getDescription() { //음료 명에 첨가물명을 추가한다. return beverage.getDescription() + ", 모카"; } //CondimentDecorator는 Beverage를 확장 하죠 @Override public double cost() { //음료 가격에 모카 가격을 추가한다. return .20 + beverage.cost(); } } |
StarbuzzCoffee.java public class StarbuzzCoffee { public static void main(String[] args) { //에스프레소 커피 Beverage espresso = new Espresso(); System.out.println(espresso.getDescription()+ " : $"+espresso.cost()); //다크로스트 커피 + 모카+ 모카 + 휘핑크림 Beverage darkRoast = new DarkRoast(); //다크로스트 커피 darkRoast = new Mocha(darkRoast); //모카 추가 darkRoast = new Mocha(darkRoast); //모카 한번 더 추가 darkRoast = new Whip(darkRoast); //휘핑크림 추가 System.out.println(darkRoast.getDescription()+ " : $"+darkRoast.cost()); //하우스 블렌드 커피, 두유, 모카, 휘핑크림 Beverage houseBlend = new HouseBlend(); //하우스 블렌드 커피 houseBlend = new Soy(houseBlend); //두유 추가 houseBlend = new Mocha(houseBlend); //모카 추가 houseBlend = new Whip(houseBlend); //휘핑크림 추가 System.out.println(houseBlend.getDescription()+ " : $"+houseBlend.cost()); } } | 에스프레소 커피 : $1.99 다크 로스트 커피, 모카, 모카, 휘핑크림 : $1.49 하우스 블렌드 커피, 두유, 모카, 휘핑크림 : $1.34 |
2. 데코레이터 패턴(Decorator Pattern) 정의
2.1 데코레이터 패턴(Decorator Pattern) ?
- 데코레이터 패턴에서는 객체의 추가적인 요건을 동적으로 추가한다.
- 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.
2.2 데코레이터의 단점
- 데코레이터 패턴을 이용해 디자인을 하다 보면 잡다한 클래스들이 많아 질 수 있다.
- 겹겹이 애워싼 객체의 정체를 알기가 힘들다.
3. 데코레이터가 적용된 예 : 자바 I/O
- java.io 패키지에는 어마어마하게 많은 클래스들이 있지만, 많은 부분이 데코레이터 패턴을 바탕으로 만들어져 있다.
- 스타 버즈 디자인하고 별로 다르지 않죠? 출력 스트림의 디자인도 똑같다.
- 자바 I/O를 보면 데코레이터의 단점도 발견 할 수 있다.
데코레이터 패턴을 이용해서 디자인을 하다 보면 잡다한 클래스들이 너무 많아 진다.
3.1 자바 I/O 데코레이터
LowerCaseInputStream.java
//InputStream의 추상 데코레이터인 FilterInputStream을 확장 합니다. public class LowerCaseInputStream extends FilterInputStream { public LowerCaseInputStream(InputStream in) { super(in); } public int read() throws IOException { int c = super.read(); return (c == -1 ? c : Character.toLowerCase((char)c)); } public int read(byte[] b, int offset, int len) throws IOException { int result = super.read(b, offset, len); for (int i = offset; i < offset+result; i++) { b[i] = (byte)Character.toLowerCase((char)b[i]); } return result; } }
LowerCaseTest.java
public class LowerCaseTest { public static void main(String[] args) { int c; try{ InputStream in = new LowerCaseInputStream( new BufferedInputStream( new FileInputStream("C:/test.txt"))); while((c=in.read()) >= 0){ System.out.print((char)c); } in.close(); }catch(IOException ioe){ ioe.printStackTrace(); } } }
4. 데코레이터 패턴 핵심정리
- 상속을 통해 확장을 할 수도 있지만, 디자인 유연성 면에서는 별로 좋지 않다.
- 기존 코드를 수정하지 않고도 행동을 확장하는 방법이 필요하다.
- 구성과 위임을 통해서 실행중에 새로운 행동을 추가할 수 있다.
- 상속대신 데코레이터 패턴을 통해서 행동을 확장 할 수 있다.
- 데코레이터 패턴에서는 구상 구성요소를 감싸주는 데코레이터들을 사용한다.
- 데코레이터의 수퍼클래스는 자신이 장식하고 있는 객체의 수퍼클래스와 같다.
- 데코레이터 패턴을 사용하면 자질한 객체들이 많이 추가될 수 있고, 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해 질 수 있다.
문서에 대하여
- 최초작성자 : 김정식
- 최초작성일 : 2008년 03월 01일
- 이 문서는 HeadFirst Design Patterns을 정리한 내용 입니다.
- 이 문서는 오라클클럽 자바 웹개발자 스터디 모임에서 작성하였습니다.
- 이 문서를 다른 블로그나 홈페이지에 퍼가실 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^
문서정보
- 이 문서는 오라클클럽에서 작성하였습니다.
- 이 문서를 다른 블로그나 홈페이지에 게재하실 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^
- 출처 : http://www.gurubee.net/pages/viewpage.action?pageId=1507398&
- 오라클클럽 지식창고의 모든 문서는 크리에이티브 커먼즈의 저작자표시-비영리-동일조건변경허락(BY-NC-SA) 라이선스에 따라 자유롭게 사용할 수 있습니다.
'디자인패턴' 카테고리의 다른 글
팩토리 패턴(Factory Pattern) (0) | 2014.11.11 |
---|---|
옵저버 패턴(Observer Pattern) (0) | 2014.11.11 |
데코레이터 패턴(Decorator Pattern) (0) | 2014.11.11 |
어댑터 패턴(Adapter Pattern) (0) | 2014.11.11 |
싱글톤 패턴 (0) | 2012.11.02 |