Lee's Grow up

[디자인패턴/Design Pattern] Decorator Pattern / 데코레이터 패턴 본문

PROGRAMMING/디자인패턴

[디자인패턴/Design Pattern] Decorator Pattern / 데코레이터 패턴

효기로그 2019. 12. 12. 14:03
반응형

관련 내용은 [자바 언어로 배우는 디자인 패턴 입문],[Head First Design Pattern]의 내용을 참고해서 정리한 내용입니다. 잘못된 부분은 댓글로 피드백 주시면 감사하겠습니다.

1. Docorator 패턴이란?


객체에 추가적인 요건을 동적으로 첨가할때 사용하는 방식으로, 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공합니다.

2. Decorator 패턴의 등장인물


  • Component
    1. 기능을 추가할 때 핵심이 되는 역할로, 해당 기능의 인터페이스(API)만을 결정합니다.
  • ConcreteCompoent
    1. Component를 실제로 구현하는 역할입니다.
  • Decorator(장식자)
    1. Component와 동일한 인터페이스(API)를 가지며, 구체적인 장식자의 Component 역할을 합니다.
  • ConcreteDecorator(구체적인 장식자)
    1. Decorator를 시렞로 구현하는 역할입니다.
2-1. Decorator 패턴의 클래스 다이어그램

3. 예제


여러분이 스타OO과 같은 음료회사의 주문시스템을 만들어야 하는 상황이 생겼다고 가정했을 때, 음료라는 큰 슈퍼클래스를 두고 음료들을 서브클래스로 두어서 구현을 하려고 할 것입니다.
그런데 음료의 갯수가 너무 많아진다면? 또한 휘핑크림 추가나 우유 추가등 첨가물에 따라서 동작이나 가격이 달라진다면? 엄청나게 많은 클래스가 필요할것이고, 휘핑크림의 가격이 변동되면 수정해야 될 클래스들도 상당할 것이다.

3-1. 예제 클래스 다이어그램

이와 같은 상황에 여러분들은 공통되는 부분을 추출하고자 첨가물인 밀크, 휘핑크림등을 슈퍼클래스의 구성으로 가지면 밀크가 들어간 에스프레소, 휘핑크림이 들어간 에스프레소 등을 하나의 에스프레소 클래스로 줄일 수 있지 않을까라는 생각이 들 수도 있습니다.

3-2. 첨가물을 구성으로 가지는 슈퍼 클래스
public abstract class Beverage {
    private Milk milk ;
    private Whip whip;
    // 기타 필요한 필드 및 생성자는 생략

    public void addMilk() { 
        // 우유 추가 동작 구현
    }
    // milk 관련 메소드 게터세터 등등
    // 나머지 메소드도 생략

}

대충 슈퍼클래스가 위와 같은 방식으로 정의되어 있을 때, 첨가물을 구성으로 사용하여 클래스 개수도 줄이는 등 괜찮아 보일 수 있지만 아직도 아래와 같은 문제점이 존재합니다.

3-2 클래스의 문제점
  • 첨가물에 대한 수정이 있을 경우 super 클래스의 수정이 필요 ( 추가, 수정, 삭제 등 )
  • 새로운 클래스가 우유가 들어가면 안되는 경우 서브클래스에서 addMilk() 메소드가 동작 안하도록 추가 구현 필요
  • 우유를 2번 추가하는 동작이 필요하면 doubleAddMilk() 메소드가 필요

등과 같은 여러 문제가 아직 발생합니다. 이렇게 동적으로 객체에 요건을 추가할 상황이 많을 경우 데코레이터 패턴을 사용합니다.

4. Decorator 패턴을 사용한 해결


4-1. 예제에 사용 될 클래스 다이어그램

4-2. BeverageComponent 클래스

Compoent 역할로써 전체의 핵심 역할로 인터페이스(API)를 정의하는 클래스입니다. 여기서 전체 구현하는 클래스들의 '형식'을 정해줍니다. 사실상 좀 더 발전된 디자인의 방식으로는 해당 클래스도 abstract가 아닌 interface로 '형식'만 정의해주는 방식이 가장 이상적이지만
예제에서는 기존에 BeverageComponent라는 추상클래스가 존재할 때 변경방법을 소개하는 방향이여서 추상 클래스를 써야 하는 상황이라면 그냥 추상클래스로 구성하는게 더 나을수도 있습니다. 즉 구현하는 시점에 좀 더 비용이 저렴한 방식을 사용하면 됩니다.

public abstract class BeverageComponent {
    protected String name;

    public String getName() {
        return name;
    }
    public abstract int cost();
}
4-3. CondimentDecorator 클래스

Decorator의 역할로, Compoent역할인 BeverageComponent를 상속받으며, ConcreteDecoratorCompoent를 역할을 수행합니다.

public abstract class CondimentDecorator extends BeverageComponent {
    public abstract String getName();
}
4-4. Hazelnut 클래스

ConcreteDecorator의 역할로 CondimentDecorator를 상속받아 내용을 정의해줍니다.
핵심은 필드로 BeverageComponent 클래스를 가지며, 확장의 대상이 될 객체를 참조시킵니다.
나머지 Milk, Mocha 도 필요에 따라 정의해주시면 됩니다.

public class Hazelnut extends CondimentDecorator {
    BeverageComponent beverage;

    public Hazelnut(BeverageComponent beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getName() {
        return "헤이즐넛 시럽 추가, " + beverage.getName() ;
    }

    @Override
    public int cost() {
        return 500 + beverage.cost();
    }
}
4-5. Main 클래스
public class Main {
    public static void main(String[] args) {

        BeverageComponent beverage = new Americano();
        System.out.println(beverage.getName() +" 가격 : " + beverage.cost());

        BeverageComponent beverage2 = new Americano();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Hazelnut(beverage2);
        System.out.println(beverage2.getName() +" 가격 : " + beverage2.cost());

    }
}

beverage2를 보시면 생성자를 통해 확장시키고 싶은 객체를 참조시키며 자신으로 감싸는 모습이 보입니다.
이렇게 확장이 필요할 경우 원하는 만큼 객체를 감싸면서 객체를 확장해 갈 수 있는 방식입니다.

4-5 실행결과
아메리카노 가격 : 2000
헤이즐넛 시럽 추가, 모카 추가,  아메리카노 가격 : 3300
우유 추가 , 아메리카노 가격 : 3500
왜 사용할까?
  • 추가 첨가물(휘핑크림)인 Whip 클래스가 추가 된다고 가정했을 때, 그냥 Whip 클래스를 생성해주고, CondimentDecorator 클래스를 상속받아 내용만 정의해주면 기존 코드에 수정없이 클라이언트는 Whip 첨가물을 사용할 수 있게 됩니다.

5. 관련 패턴


  • Adapter 패턴 : Decorator 패턴은 내용을 변경없이 장식을 추가하는 패턴이고, 해당 패턴은 다른 두개의 인터페이스를 연결해줍니다. link
  • Strategy 패턴 : 알고리즘 자체를 변경해서 기능을 확장이 아닌 변경합니다. link

 

6. 비교하기


디자인 패턴을 배우다 보니까 Adapter , Decorator, Facade 가 얼핏 보면 비슷하다고 느낌이 들어서 차이점을 정리합니다.

  • Adapter : 관계가 없는 인터페이스(API)를 연결할 때 사용, 호환성 link
  • Decorator : 인터페이스(API)를 바꾸지 않고 기능을 추가. 확장성 link
  • Facade : 인터페이스(API)를 간편하게 사용. 간편함 link
반응형
Comments