[디자인 패턴] 데코레이터 패턴

이 글은 카페 주문 시스템에 데코레이터 패턴을 적용한 예제를 이용해 개념을 설명합니다.

문제 제기

아래의 클래스 다이어그램은 모 카페의 초창기 주문 시스템 클래스의 구성이다.

클래스 다이어그램 설명

  • Beverage 클래스는 음료를 나타내는 추상 클래스
  • 매장에서 판매되는 모든 음료는 Beverage 클래스의 서브 클래스

이 구성의 문제점

  1. 음료의 종류 뿐 아니라 고객이 추가하는 옵션(우유, 두유, 모카, 휘핑크림 등)이 들어간 모든 경우의 수에 서브 클래스를 작성해야 하므로 클래스의 수가 너무 많아짐.
  2. 첨가물 가격이 바뀔 때마다 기존 코드를 수정해야 함. => OCP 원칙 위반

OCP(Open-Closed Principle)는 '소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다'는 프로그래밍 원칙

장식하기(decorate)

  1. DarkRoast 객체를 가져옴. - beverage로 부터 상속
  2. 첨가물 Mocha 객체로 장식. - decorator
  3. 첨가물 Whip 객체로 장식. - decorator
  4. cost()를 호출. 이 때 첨가물의 가격을 계산하는 일은 해당 객체에게 위임.

데코레이터 패턴 정의

데코레이터 패턴(Decorator Pattern)으로 객체에 추가 요소를 동적으로 더할 수 있음.
데코레이터를 사용하면 서브 클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있음.

  • 데코레이터의 슈퍼클래스는 자신이 장식하고 있는 객체의 슈퍼클래스와 같음.
  • 한 객체를 여러개의 데코레이터로 감쌀 수 있음.
  • 데코레이터는 자신이 감싸고 있는 객체와 같은 슈퍼 클래스를 가지고 있어 원래 객체(싸여있는 객체)가 들어갈 자리에 데코레이터 객체를 넣어도 무관.
  • 데코레이터는 자신이 장식하고 있는 객체에게 어떤 행동을 위임하는 일 말고도 추가 작업을 수행할 수 있음.
  • 객체는 언제든지 감쌀 수 있으므로 실행 중에 필요한 데코레이터를 마음대로 적용할 수 있음.

데코레이터 패턴 적용

예제 코드

Base

public abstract class Beverage {
    protected String description = "음료입니다.";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}
public abstract class CondimentDecorator extends Beverage {
    protected Beverage beverage;
    public abstract String getDescription();
}

Beverages

public class Espresso extends Beverage {
    public Espresso() {
        description = "에스프레소 커피";
    }

    public double cost() {
        return 1.99;
    }
}
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "하우스 블렌드 커피";
    }

    public double cost() {
        return 0.89;
    }
}

Condiments

public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return beverage.getDescription() + ", 모카";
    }

    public double cost() {
        return beverage.cost() + 0.20;
    }
}
public class Whip extends CondimentDecorator {
    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return beverage.getDescription() + ", 휘핑크림";
    }

    public double cost() {
        return beverage.cost() + 0.15;
    }
}

예제용 클래스

public class Coffee {
    public Coffee() {
        Beverage espresso = new Espresso();
        System.out.println(espresso.getDescription() + ": $" + espresso.cost());

        Beverage darkRoast = new Whip(new Mocha(new HouseBlend()));
        System.out.println(darkRoast.getDescription() + ": $" + darkRoast.cost());
    }
}

예제 출력

에스프레소 커피: $1.99
하우스 블렌드 커피, 모카, 휘핑크림: $1.24

단점

  1. 잡다한 클래스가 엄청나게 추가되어 이해하기 힘든 디자인이 만들어질 수 있음.
  2. 특정 형식에 의존하는 코드에 데코레이터를 무턱대고 적용하면 모든게 엉망이 됨.
  3. 구성 요소를 초기화하는데 필요한 코드가 훨씬 복잡해질 수 있음.

장점

  1. 객체에 추가 요소를 동적으로 더할 수 있음. => OCP 원칙에 부합
  2. 서브클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있음.

참조

  • 헤드퍼스트 디자인패턴 (도서)

좋은 웹페이지 즐겨찾기