ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Design Pattern] Decorator Pattern
    Learn/Architecture 2022. 9. 24. 17:07

    # 개요

    이미 존재하는 객체를 dynamic wrapping하여 책임과 행동을 추가하는 패턴

    • wrapping한 객체에 새로운 기능을 추가

     

    # 문제 상황

    아래는 커피숍에 대한 예시이다. 

    만약 새로운 첨가물을 추가하고 싶다면 아래와 같이 할 수 있다. 

    그러나, 클래스가 너무 많다. 

    물론 attribute로 표현하면 클래스가 늘어나는 것은 막을 수 있다. 

    그러나 Beverage는 상위 클래스이므로 변경이 잘 없어야 하지만

    아래와 같이 변경이 일어날 때 마다 cost함수 변경이 필요하다. (OCP 위반)

    public class Beverage {
        protected String description;
        boolean milk, soy, mocha, whip;
        public float cost () {
            float condimentCost = 0.0;
            if (hasMilk())
                condimentCost += milkCost;
            if (hasSoy())
                condimentCost += soyCost;
            if (hasMocha())
                condimentCost += mochaCost;
            if (hasWhip())
                condimentCost += whipCost;
            return condimentCost;
        }
    }

    OCP를 적용하여 해결할 수도 있는데, 너무 과도하게 적용하면 좋지 않다는 점에 유의해야 한다. 

     

    # 적용 예시

    위 예시를 다음과 같이 해결한다. 

    객체를 wrapping하고 연쇄적으로 자신의 안에 있는 객체에게 필요한 정보를 물어보면서 계산한다. 

     

    클래스 다이어그램으로 일반화하면 다음과 같다. 

    Decorator를 중심으로 ConcreteDecorator들이 추가된다. 

     

    여기서 중요한 점이 두 가지 있다. 

    1. Decorator와 Component는 1:1 관계이다. (Association)

    2. Decorator는 Component를 상속 받는다. (Inheritance) 

     

    Association과 Inheritance에 대한 화살표가 모두 다 있어야 함에 유의하자. 

     

    위의 커피 예제에 이 방식을 적용하면 다음과 같다. 

    코드로 표현하면 다음과 같다. 

    public abstract class Beverage {
        protected String description = “Unknown Beverage”;
        
        public String getDescription() {
            return description;
        }
        
        public abstract double cost();
    }
    
    public class Espresso extends Beverage {
        public Espresso() {
            description = “Espresso”;
        }
        
        public double cost() {
            return 1.99;
        }
    }

    Beverage 클래스의 cost 메서드는 하위 클래스에서 구현할 수 있도록 abstract method로 구현되어 있다. 

     

    Extends로 받으면 상위 객체의 메서드, 변수를 그대로 사용할 수 있다. 

    public abstract class CondimentDecorator extends Beverage {
        protected Beverage beverage;
        public abstract String getDescription();
    }
    
    public class Mocha extends CondimentDecorator {
        public Mocha(Beverage beverage) {
            this.beverage = beverage;
        }
        
        public String getDescription() {
            return beverage.getDescription() + “, Mocha”;
        }
        
        public double cost() {
            return .20 + beverage.cost();
        }
    }

    Decorator의 생성자에서는 beverage 인스턴스가 들어오면 자기 자신의 것으로 갖는다. 

     

    cost는 자신의 cost와 꾸미고 있는 객체의 cost를 더해서 계산한다. 

     

    테스트 코드는 다음과 같다. 

    public class StarbuzzCoffee {
        public static void main(String args[]) {
            Beverage beverage = new Espresso();
            System.out.println(beverage.getDescription() + “ $” + beverage.cost());
            
            Beverage beverage2 = new DarkRoast();
            beverage2 = new Mocha(beverage2);
            beverage2 = new Mocha(beverage2);
            beverage2 = new Whip(beverage2);
            System.out.println(beverage2.getDescription() + “ $” + beverage2.cost());
            
            Beverage beverage3 = new HouseBlend();
            beverage3 = new Soy(beverage3);
            beverage3 = new Mocha(beverage3);
            beverage3 = new Whip(beverage3);
            System.out.println(beverage3.getDescription() + “ $” + beverage3.cost());
        }
    }

    인스턴스를 만들어 줄 때 마다 cost가 추가된다. (런타임에 실행)

     

    ## 상속을 잊지 말자

    위에서 정리했듯 Association과 더불어 Inheritance가 꼭 필요하다. 

     

    아래 그림을 보면 상속 관계에 의해 Component에서 정의한 methodA, methodB를 Concrete Component와 Concrete Decorator들에서 링크할 수 있다. 

     

    Decorator Pattern에서는 wrapping이 연쇄적으로 일어나므로 Concrete Component를 Concrete Decorator 1, 2가 연쇄적으로 wrapping 할 수 있는데 상속이 없으면 Concrete Decorator2가 Concrete Decorator 1을 가르킬 수 없다. 

     

    상속이 있어야 Component 타입을 가르킬 수 있으므로 hierarchy를 만들 수 있다. 

     

    # 관련된 패턴

    인터페이스와 관련된 패턴들과 비교해 볼 수 있다.  

    Adapter Pattern: 서로 상이한 인터페이스를 맞춰주는 패턴. (different interface)

    Proxy Pattern: 실제 사용할 subject와 같은 인터페이스를 proxy가 제공한다. (same interface) 

    Decorator Pattern: 기존의 객체에 새로운 책임을 부여한다.  (enhanced interface)

     

    # 요약

    • Open-Closed Principle (OCP)를 만족하는 패턴
    • 새로운 책임을 부여할 수 있고 하위 클래스가 너무 많아지는 것을 막을 수 있다. 
    • Composition & Delegation
    • Decorator Class는 꾸미고 있는 컴포넌트와와 동일한 타입의 부모를 상속받아서 연쇄적으로 wrap할 수 있다. 
    • 자잘한 클래스를 여러 개 만들고 해당 패턴을 모르는 사람은 이해하기 어려운 단점이 있다. 

    'Learn > Architecture' 카테고리의 다른 글

    [Design Pattern] Bridge Pattern  (0) 2022.09.25
    [Design Pattern] Composite Pattern  (0) 2022.09.25
    [Design Pattern] Adapter Pattern  (0) 2022.09.04
    [Design Pattern] Singleton Pattern  (0) 2022.08.31
    [Design Pattern] Builder Pattern  (0) 2022.08.30

    댓글

Designed by Tistory.