Whiteship's Note


데코레이터(Decorator) 패턴

Design Pattern : 2008.10.01 11:52


참조:
Java 언어로 배우는 디자인 패턴 입문

- 장식과 내용물을 동일시하기: 투과적인 인터페이스를 두고 객체에 기능들을 추가하여 목적으로 하는 객체를 만들어 나가는 방식.
- Component: 꾸밀 대상이 되는 요소, 인터페이스 or 추상 클래스
- Concrete Component: 컴포넌트 구현체
- Decorator: 장식물 인터페이스, 컴포넌트와 동일한 인터페이스를 가지며(extends), 자신의 멤버로도 가지고 있다.(delegation) 
- Concrete Decorator: 장식물 구현체.

사용자 삽입 이미지
출처: http://en.wikipedia.org/wiki/Decorator_pattern

- 내용을 바꾸지 않고 기능을 추가할 수 있다.
- 투과적인 인터페이스

'Design Pattern' 카테고리의 다른 글

[OOAD] 객체지향 원칙 1. SRP  (2) 2010.05.07
[Tell, Don't Ask] 물어보지 말고 시켜라  (8) 2010.04.07
Visitor 패턴  (2) 2010.03.12
Holub on Pattern 좋은데요~  (4) 2008.12.07
프로토타입 패턴(Prototype Pattern)  (2) 2008.10.31
데코레이터(Decorator) 패턴  (0) 2008.10.01
프록시(Proxy) 패턴  (0) 2008.09.26
JUnit 공부하자.  (0) 2008.09.01
H.F.OOAD 5장 문제  (0) 2007.11.20
상위 클래스 보다는 인터페이스를...  (4) 2007.08.31
Singletons and lazy loading  (2) 2007.01.27
top

Write a comment.


Decorator Pattern 예제

Design Pattern : 2006.10.20 16:35


데코레이터 패턴 레포트 상세 내용

게임게발을하고 있습니다. 각 케릭터는 인간이라는 한 종족으로시작을 하게 되며

한 종족은 전사, 마법사, 도둑세 가지의 직업을 가지게 됩니다. 각 케릭터가 착용하게 되는 아이템은 무기, 방어구를 착용하게 됩니다. 그리고 모든 직업은 공통적으로 HP, MP라는 속성값을 가지게 됩니다.

케릭터를생성하여 전사, 마법사, 도둑 세가지의 직업으로 나누어서각 케릭이 어떠한 직업을 가지고 어떠한 이름을 가지고 있는지 보여줍니다. 그 후 아이템을 습득하여 무기및 방어구를 습득하여서 어떠한 아이템을 착용했는지 그리고 그 아이템은 어떠한 상세 내용을 가지고 있는지에 대한 메시지를 창에 보여주게 됩니다.

또한케릭터가 독에 걸리거나 상처를 입는 등 상태이상에 걸리게 되면 케릭터가 상태이상에 걸렸다는 메시지를 창에 보여주면 됩니다.

각 아이템은 다음 예제와 같은 상세한 세부내용이 있습니다.

무기 예제

단검                               데미지 1~10

롱소드                             데미지 10~20

바스타드 소드                      데미지 10~20

방어구 예제

가죽 갑옷                          방어 확률 10%

플레이트 아머                      방어 확률 20%

풀플레이트 아머                    방어 확률 30%

상태이상은 다음 예제와 같은 상세한 세부내용이 존재합니다.

가시독

이동속도가 느려집니다.

맹독

케릭터의 HP가 감소됩니다.

마나독

케릭터의 MP가 감소됩니다.

질병

질병

케릭터의 HP, MP가 감소됩니다.

상처

케릭터가 부상을 입어 활동을 할 수 없습니다.

오.. 지쟈스.. 클래스가 몇개인고.. 이것도 월요일까지 제출해야 한다는거~

오늘 일단 이것부터 해결해야겠군요..

top

Write a comment.


3장. Decorator Pattern(계속)

Design Pattern : 2006.10.17 17:38


이 전 글을 보시면 새로운 첨가물이 추가 될 때마다 Beverage class가 변하게 됩니다. 구체적으로 cost() 메소드가 변하게 되는 거죠. 이 말은 다시 새로운 Class가 추가될 때 마다 기존 Class가 변하게 되고 따라서 다시 컴파일 -> 배포 과정을 거쳐야 한다는 것인데.. 이러면 상당히 불편합니다. 새로운 class를 추가하면서도 기존의 class가 변하지 않을 그런 방법 없을까요? 이것과 관련된 원칙이 하나 있습니다.

OCP(Open-Closed Principle)는 가장 중요한 디자인 원칙 중 하나라고 합니다.

디자인 원칙6
클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.

기존 코드는 변경에 닫혀 있어야 하고 새로운 행동을 마음 껏 추가할 수 있어야 한다는 말인데요. 자칫 모순 같아 보이지만 상황이나 문맥을 잘 살펴보시면 분명 모순되는 말은 아닙니다.

그리고 이것과 관련된 ZDnet의 글도 있는데요. 읽어봤는데 H.F보다 더 많은 내용(H.F.는 2페이지, 저 글은 13페이지)을 적어 놓은 듯하네요.

이제 OCP를 달성하게 해주는 디자인 패턴 들 중에 하나로 Decorator Pattern을 배워 봅시다. 이름에서도 벌써 어떤 패턴일지 대강 감이 오시지 않나요.

객체를 장식(실제로는 감싸는 wrapping(?)이라고 생각하시면 쉬울 듯 합니다.)하고 또 다시 그 객체를 장식하고..그러한 식으로 구성하는 것이 가능하도록 해주는 패턴입니다.

여기 DartRoast 객체가 있습니다. 이 객체는 Beverage를 상속 받은 음료 수 중 하나죠. 따라서 cost() 메소드를 가지고있을 것입니다. 이 객체에 Mocha(첨가물 중 하나입니다.)를 얻으면 가격이 추가되겠죠. 이 때 이 첨가물로 DarkRoast를 감싸는 것입니다.

이렇게 Mocha로 DarkRoast를 감싼 뒤 가격을 구하고 있다면 Mocha에 있는 cost()를 호출하는 거죠. 그러면 그 메소드 안에서는 Mocha가 가지고 있는 DarkRoast의 cost()를 호출 한 뒤에 거기에 자신의 가격(모카가격)을 더하면 되겠습니다. 즉 여기서 Mocha가 DarkRoast를 감싼 다는 것이 Mocha가 DarkRoast를 멤버로 가지고 있다고 생각하시면 되겠습니다.
이러한 순으로 가격 계산하는 과정이 진행됩니다.

이제 데코레이터 패턴의 정의를 살펴봅시다.

데코레이터 패턴에서는 객체에 추가적인 요건을 동적으로 첨가한다. 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.


하지만 단순이 상속을 통해서만 그렇게 자유로워 지는 것은 아니고 컴포지션도 한몫합니다. 결국 상속과 컴포지션의 합작품이지요.

클래스 다이어그램으로 살펴 봅시다.


wikipidia에 있는 그림입니다. 새로 그리기가 귀찮아서요 ^^;;. 저는 이 다이어그램을 보고 처음에 굉장히 놀랬었습니다. 왜냐면 상속과 컴포지션을 동시에 사용했기 때문이지요. 클래스를 재사용하는 방법 중에 가장 먼저 떠오르는 두가지 방법을 그것도 동시에 두 클래스 사이에 모두 사용한 것이 제 사고의 편협함을 꼬집어 주더군요. 상속하든가.. 컴포지션 하든가.. 이런 생각을 가지고 있던 저는 마치 흑백논리에 빠져있었던 사람 같은 기분이였습니다.

위 다이어그램을 보시면 Decorator가 Component를 상속하면서 동시에 컴포지션으로 상위 클래스 타입의 멤버를 가지고 있습니다. 이 말은 상위 클래스의 모든 하위 클래스들을 Decorator에서 멤버 변수로 가지고 있을 수 있다는 뜻인데요. Decorator 자신도 또한 Component의 하위 클래스 이므로.. 자기 자신을 멤버 변수로 가지고 있을 수 있다는 것입니다. 물론 ConcreteComponent들도 Decorator의 들이 멤버 변수로 가지고 있을 수 있겠죠.

이 놀라움을 감상하며 잠시 시간을 보낸 뒤 다음에는 스타버즈 커피숍의 문제에 적용해보도록 하겠습니다.


ps : 여기서 궁금한 것은 데코레이터 패턴이 확장에 열려있게 된 것은 알 수 있었습니다. 하지만 변화에는 어떻게 닫혀 있다는 것일까요.. 이 패턴을 사용함으로 써 더이상 cost() 메소드에 변화가 가지 않게 되어 변화에 닫히게 되었다는 것일까요?.. ZDnet의 글을 보신 분들은 interface를 사용하여 변화에 닫히고 확장에 열리는 설계를 보셨을것입니다. 여기서는 interface가 보이지 않는데.. 어떻게 된 일일까요.. 좀더 생각히 필요하네요..

top

  1. Favicon of https://whiteship.tistory.com BlogIcon 기선 2006.10.22 21:08 신고 PERM. MOD/DEL REPLY

    변화에 닫혀있다는 말은 새로운 클래스를 추가하여도 기존의 코드가 변경되지 않거나 변하는 부분이 최소화 되어있다는 말을 의미하니까.. 데코레이터 패턴은 새로운 데코레이터 클래스들이 추가 되더라도 기존의 코드에 영향을 주지 않기 때문에.. 변화에 닫혀 있다는 것 같군요.

Write a comment.


3장 Decorator Pattern

Design Pattern : 2006.10.17 16:39



이번에는 스타버즈의 메뉴를 class diagram으로 그린 것입니다.
기본적인 Beverage class를 상속 받는 방식으로 새로운 메뉴를 생성하고 있습니다.
만약..커피를 주문할 때 우유,  두유, 모카, 크림과 같은 첨가물을 추가하는 경우 각각이 다른 class로 생성하여 상속 받게 됩니다. 즉, 모카 커피 class, 두유 커피 class, 우유 커피 class, 크림 커피 class, 우유 모카 커피 class..... 이런 식으로 하위 class가 무지막지하게 늘어나게 됩니다. 만약에 새로운 첨가물 하나가 추가 된다면 추가되는 class는 하나가 아닐 것입니다.
이런 방법은 별로 좋치 않겠군요.. 그래서 Beverage class에 첨가물들이 들어 있는지에 대한 boolean 변수(플래그-flag)를 만들어 두고 cost() 메소드 안에서는 첨가물이 들어있는지 확인하고 첨가되어있는 첨가물의 가격을 전부 더하는 내용을 넣어 둡니다.
그리고 이것을 상속받는 하위 클래스에서는 cost() 메소드를 상속받은 후 자기 자신의 가격(커피, 에스프레소..등의 자체 가격)에 super.coast()를 호출하여 첨가물의 가격을 구해서 더하면 될 것입니다.
따라서 Beverage class가 다음과 같이 달라집니다.

Beverage class에 있는 cost() 메소드는 다음과 같습니다.
public class Berverage{
  public double cost(){
   if(hasMilk())
      cost += milkCost;
   if(hasSoy())
      cost += soyCost;
   ...
   return cost;
  }
}
이렇게 첨가되어 있는(hasXXX) 첨가물의 가격을 더해서 return합니다.
그러면 하위 class들의 cost에서는
public class HouseBlend{
  public double cost(){
   cost += super.cost();
   return cost;
  }
}
이렇게 해서 자기 자신(houseBlend의 가격)에 첨가물의 가격(super.cost())을 더해서 return하면 됩니다.
일단은 이 방법이 맨 처음에 사용했던 방법보다 간단해 보입니다.
적어도 class가 기하급수적으로 늘어나진 않으니깐요..
하지만 새로운 첨가물이 추가 될 때 마다 Beverage에 새로운 변수가 두개(첨가물의 가격, 첨가물의 첨가 여부를 나타내는 boolean변수)와 메소드 두개(setter와 hasXXX())가 생기며 cost() 메소드에 if문이 추가 되겠네요.
그리고 만약에 녹차가 새로 추가 됐는데 녹차에 크림을 얹는 일이 발생할 수도 있겠네요.
1장에서 장난감 오리가 날수 있던 것과 마찬가지 문제입니다.
앗 그리고 첨가물을 한번밖에 추가를 못하는 군요..
흠... 이런 문제를 해결하기 위해서든 뭔가 근본적인 디자인을 바꿔야 할 것 같군요..
다음에 계속 살펴보겠습니다.
top

Write a comment.