객체에 추가 요소를 동적으로 더할 수 있습니다. 데코레이터를 사용하면 서브클래스를 만들 때보다 훨씬 유연하게 기능을 확장할 수 있습니다.
커피숍을 예시로 들어보자.
Beverage라는 인터페이스를 두고 이를 구현하는 클래스들(아메리카노, 에스프레소..) 등을 구현할 수 있다.
그렇다면 스타벅스를 생각해보자.
스타벅스에서 커피를 시킬 때 추가 옵션을 붙여서 시킬 수 있다. 이러한 부분을 어떻게 나타낼 것인가?
모두 Beverage 인터페이스를 두고 휘핑크림을 추가하고, 추가하고.. 추가하고.. 이러한 것들을 하나하나 구현시킨다면 클래스가 말 그대로 폭발할 것이다.
⇒ 바뀌는 부분과 바뀌지 않는 부분을 나누지 않았다. 이를 나눠보자.
⇒ 상속보다는 구성을 활용해보자.
이러한 꾸며주는 것들을 Beverage를 추상 클래스로 변환하여 속성값을 지니도록 하는 방법이 있을 것이다.
휘핑 크림, 우유, 모카등등을 속성으로 지니고 있게 만든다.
그 다음 자식 클래스에서 해당 속성을 변경하게 만들고 부모클래스에서 이를 바탕으로 가격을 계산한다.
괜찮아보인다..? 하지만 새로운 첨가물이 들어온다면?
만약 DarkRoast커피에 모카를 넣고 휘핑크림을 추가한다고 해보자.
그렇다면 DarkRoast객체를 생성하고 이를 모카 객체로 감싸고, 휘핑크림 객체로 감싸면 된다.
즉, 장식을 한다는 말이다. 그래서 데코레이터 패턴이다. 다음과 같은 구조를 가진다.
먼저 다크로스트, 아메리카노와 같은 기본적인 구현체들은 ConcreteComponent
에 담긴다.
그 다음 휘핑 크림, 간장, 설탕과 같은 꾸며주는 객체들은 Decorator
을 상속받아 ConcreteDecorator
에서 구현한다.
이렇게 꾸며주는 객체와 일반 객체는 같은 슈퍼클래스를 지닌다.
코드로 한 번 봐보자.
public abstract class Beverage {
public String description = "";
public String getDescription() {
return description;
}
public abstract double cost();
}
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "하우스 블랜드 커피";
}
@Override
public double cost() {
return 0.99;
}
}
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "다크 로스트 커피";
}
@Override
public double cost() {
return 1.99;
}
}
public abstract class CondimentDecorator extends Beverage {
Beverage beverage;
public abstract String getDescription();
}
public class Whip extends CondimentDecorator {
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return 1.99 + beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 휘핑크림";
}
}
public class Soy extends CondimentDecorator {
public Soy(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return 0.99 + beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription() + ", 간장";
}
}
public class Main {
public static void main(String[] args) {
Beverage coffee = new Soy(new Whip(new DarkRoast()));
System.out.println(coffee.cost());
System.out.println(coffee.getDescription());
}
}
HouseBlend, DarkRoast이러한 객체를 Decorator로 감싸버리면 그 다음에는 얘네가 누군지 모르잖아요. 라는 뜻이다. 맞는 말이다.
그래서 구상 구성 요소에 어떠한 작업을 해야한다면 코드가 제대로 작동하지 않을 수 있다.
이 때에는 데코레이터 패턴을 적용하는게 옳을 지 생각해봐야 한다.
참고
헤드퍼스트 디자인 패턴
https://dailyheumsi.tistory.com/198