FIF's 코딩팩토리

데코레이터 패턴(Decorater Pattern) 정리 본문

Back-End/Design Pattern(디자인 패턴)

데코레이터 패턴(Decorater Pattern) 정리

FIF 2019. 6. 5. 14:47
반응형

데코레이터 패턴(Decorator Pattern) 정의

객체에 추가적인 요건을 동적으로 첨가한다.

데코레이터는 서브클래스 만드는 걸 통해 기능을 유연하게 확장할 수 있는 방법을 제공한다.

 

데코레이터 패턴 클래스 다이어그램

1.     ConcreteComponent에 새로운 행동을 동적으로 추가할 수 있다.

2.     각 데코레이터 안에는 구성요소(Component)에 대한 레퍼런스가 들어있는 인스턴스 변수가 있다.

3.     Decorator는 자신이 장식할 구성요소(Component)와 같은 인터페이스 또는 추상 클래스를 구현한다.

4.     ConcreteDecoratorA, ConcreteDecoratorB에는 그 객체가 장식하고 있는 인스턴스 변수가 있다. 따라서 데코레이터는 Component의 상태를 확장할 수 있다.

5.     ConcreteDecoratorA, ConcreteDecoratorB에서에서 새로운 메소드를 추가할 수 있다. 하지만 일반적으로 새로운 메소드를 추가하는 대신 Component에 원래 있던 메소드를 호출하기 전, 또는 후에 별도의 작업을 처리하는 방식으로 새로운 기능을 추가한다.

 

문제 발생                               

커피가게 사업을 시작해서 주문 시스템을 도입했다고 가정한다.

초기의 모습은 이랬다.

커피를 주문할 때는 스팀 우유나 두유, 모카를 추가하고 그 위에 휘핑크림을 얹기도 한다.

그런데 각각을 추가할 때마다 커피 가격이 올라가기 때문에 주문 시스템에서도 그런 점들을 모두 고려해야 한다.

 

추가가 많을시

한눈에 봐도 변화가 필요한 듯 보인다.

 

그렇다면 슈퍼클래스에 우유, 두유, 모카 같은 옵션 여부를 나타내는 인스턴스 변수를 넣으면 어떨까?

서브클래스들에서 각각의 가격을 계산하고 슈퍼클래스에서 구현한 cost()를 호출하여 추가 가격을 구한다면 대략 이런 모습일 거다.

 

 public class Beverage{               
	public int cost(){
		// 변수 메소스
		int totCost = 0;
		if(hasMilk()) totCost += milkCost;
		if(hasSoy()) totCost += soyCost;
		if(hasMocah()) totCost += mocahCost;
		if(hasWhip()) totCost += whipCost;
        return totCost;
     } 
 }
 
 public class DarkRoast extends Beverage{
	@Override
	public int cost() {
		return 4500+super.cost();
	 }
 }

 

이렇게 하면 첨가물의 가격이 바뀔 때마다 기존 코드의 수정이 필요하고 첨가물의 종류가 많아지면 새로운 메소드를 추가해야 하고, 슈퍼클래스의 cost() 메서드도 계속해서 고쳐줘야 한다.

새로운 음료가 출시될 때 특정 첨가물이 들어가면 안 되는 경우도 있을 것이다.

하지만 여전히 슈퍼클래스에서 모두 상속받고 있다.

 

디자인 원칙

OCP(Open-Closed Principle)

클래스는 확장에 대해서는 열려 있어야 하고 코드 변경에 대해선 닫혀 있어야 한다.

 

기존의 코드는 건드리지 않은 채로 확장을 통해 새로운 행동을 간단하게 추가할 수 있는 데코레이터 패턴을 사용한다.

상속을 써서 음료 가격과 첨가물 가격을 합한 후, 가격을 계산하는 방법은 그렇게 좋은 방법이 아니다.

클래스가 어마어마하게 방대해지거나 일부 서브클래스 에는 적합하지 않은 기능을 클래스에 추가하게 되는 문제가 있다.

 

특정 음료에서 시작해 첨가물로 그 음료를 장식(Decoration) 해보기.

1.     DarkRoast 객체를 가져온다.

2.     Mocha 객체로 장식한다.

3.     Whip 객체로 장식한다.

4.     cost()메소드를 호출한다. 이때, 첨가물의 가격을 계산하는 일은 해당 객체들에 위임된다.

 

새로 디자인한 Beverage 클래스 다이어그램

 

Beverage.java

       public abstract class Beverage{     
           private String description = "Empty";
           public String getDescription(){
                      return this.description;
           }
           public abstract int cost();
 }

 

CondimentDecorator.java

 public abstract class CondimentDecorator extends Beverage{
           public abstract String getDescription();
 }

 

Espresso.java

public class Espresso extends Beverage{    
           public Espresso() {
                      this.description = "에스프레소";
           }
           @Override
           public int cost() {
                      return 3500;
           }
 }

 

HouseBlend.java

public class HouseBlend extends Beverage{
           public HouseBlend() {
                      this.description = "하우스블렌드";
           }
           @Override
           public int cost() {
                      return 2000;
           }
 }

 

Mocha.java

public class Mocha extends CondimentDecorator{
           private Beverage beverage;
           public Mocha(Beverage beverage) {
                      this.beverage = beverage;
           }
           @Override
           public String getDescription() {
                      return beverage.getDescription()+", 모카";
           }
           @Override
           public int cost() {
                      return 500+beverage.cost();
           }         
 }

 

CoffeStore.java

 public class CoffeeStore{
           public static void main(String args[]){
                      Beverage beverage = new Espresso();
                      System.out.println(beverage.getDescription()+" cost : "+beverage.cost());
                      Beverage beverage1 = new DarkRoast();
                      beverage1 = new Mocha(beverage1);
                      beverage1 = new Mocha(beverage1);
                      beverage1 = new Whip(beverage1);
                      System.out.println(beverage1.getDescription()+" cost : "+beverage1.cost());
                      Beverage beverage2 = new HouseBlend();
                      beverage2 = new Soy(beverage2);
                      beverage2 = new Mocha(beverage2);
                      beverage2 = new Whip(beverage2);
                      System.out.println(beverage2.getDescription()+" cost : "+beverage2.cost());
           }
 } 

 

데코레이터 패턴이 적용된 예 : 자바I/O

 

InputStream이 추상 구성요소이고,모든 보조 스트림의 조상인 FileInputStream이 추상 데코레이터이다.

FilterInputStream을 상속받아 구현하는 BufferedInputStream 클래스들이 구상 데코레이터이다.

InputStream을 상속받는 FileInputStream 같은 기반 스트림들은 데코레이터로 포장될 구상 구성요소 역학을 한다.

반응형
Comments