보통 클래스를 구현할 때, 상속을 통한 구현을 많이 사용한다. 객체들이 가지는 특성을 일반화하여 부모 클래스를 생성하고, 이를 상속받는 서브 클래스에서 자세한 내용을 구현하는 것이다. 보편적으로 좋은 방식이지만, 모든 상황에서 좋은 것은 아니다.
예를 들어서, 커피숍에서 음료 주문을 받기 위해서 ‘Beverage’라는 객체를 생성했다고 가정하자. 이 Beverage 클래스에는 각 음료의 이름, 들어가는 재료, 가격등의 변수가 있을 것이고, 커피숍에서 파는 다양한 음료 클래스들은(Decaf, Espresso, Dark Roast 등등)은 Beverage를 상속받아서, 각자의 가격 및 재료를 정의할 것이다.
하지만, 이러한 구현은 추후에 재료가 추가될 때마다 기존의 클래스를 수정해야하고, 특정 음료에는 불필요한 클래스가 포함되어 있을 가능성이 있다(IcedTea라는 클래스는 hasMilk, hasMoca와 같은 매서드들을 필요로 하지 않는다.) 이는 OCP를 위배하고 있다.
Decorator Pattern을 사용하면 객체에 추가요소를 동적으로 추가하여 서브클래스의 기능을 유연하게 확장할 수 있다.
앞선 예시와 같이, 상속을 통한 서브클래스 구현이 항상 좋은 결과를 낳는것은 아니라는 것을 확인했다. 그렇다면, 특정 음료에서 시작하여, 첨가물로 그 음료를 ‘장식’하는 방법은 어떠할까? 고객이 모카와 휘핑크림을 추가한 다크 로스트 커피를 주문했다면 다음과 같이 장식하는 것이다.
DarkRoast객체를 가져온다 → Mocha로 장식한다 → Whip로 장식한다 → cost()매서드를 호출한다
이러한 방식을 통해서 재구성한 코드는 다음과 같다.
public abstract class Beverage{ // 모든 음료의 원형이 되는 추상 클래스
string description;
public string getDescription(){ // 음료의 이름
return description;
}
public abstract double cost();
}
// 첨가물을 나타내는 추상클래스
public abstract class CondimentDecorator extends Beverage{
Beverage beverage;
public abstract string getDescription();
}
// 실제 음료
public class Espresso extends Beverage{
public Espresso(){
description = "에스프레소";
}
public double cost(){
return 1.99;
}
}
// 첨가물
public class Mocha extends CondimentDecorator{
public Mocha(Beverage beverage){
this.beverage = beverage;
}
public string getDescription(){
return beverage.getDescription() + ", 모카";
}
public double cost(){
return beverage.cost() + .20;
}
}
// 사용법
public static void main(string args[]){
Beverage beverage = new Espresso(); // 에스프레소 주문
beverage = new Mocha(beverage); // 모카 추가 1회
beverage = new Mocha(beverage); // 모카 추가 2회
System.out.println(beverage.getDescription());
}
////////// 결과 //////////
//에스프레소, 모카, 모카
위의 예시에서 어떤 고객이 에스프레소를 주문하고, 모카를 재료를 추가했다고 가정하자. 그렇다면, 위의 예시와 같이 에스프레소는 Mocha로 감싸지게 될 것이다. 하지만, 특정 음료에 대해서 할인을 진행한다고 하면, 원본 음료의 클래스는 추가한 재료의 클래스로 감싸지기 때문에, 원래 어떤 음료였는지 확인하기가 여려워진다는 단점이 있다. 그렇기 때문에 Decorator Pattern을 접목할 때는 이러한 경우를 주의해야 한다.
Decorator Pattern이 유연한 디자인을 하게해주는 것은 맞지만, 개발을 진행하다보면, 자잘한 클래스가 너무 많이 생성될 수 있다.