데커레이터 패턴 ( Decorator Pattern )

데커레이터는 어떤 기능에 추가적으로 기능을 덧붙이고 싶은 경우, 그 기능들을 Decorator로 만들어서 덧붙이는 방식입니다.


예를 들어, 서브웨이 샌드위치를 생각해보겠습니다.

서브웨이를 주문하면 고객의 기호에 따라 채소를 선택할 수 있습니다.

즉, 기본 빵 위에 채소와 토핑을 추가하여 샌드위치가 완성됩니다.

여기서 채소와 토핑( 양상추, 피클, 양파, 치즈 ... )들 각각이 데커레이터가 됩니다.


데커레이터 패턴을 사용하면 기능이 딱 정해져있는 객체가 아닌,

동적으로 기능을 조합하여 객체를 만드는 것이 가능해집니다.


이제 서브웨이 주문 방식을 예로 데커레이터 패턴이 필요한 이유에 대해서 알아보겠습니다.





1. 데커레이터 패턴 사용 이유

샌드위치를 만들기 위해서는 기본적으로 빵( Bread )이 필요합니다.

그리고 토핑으로 양상추( lettuce ), 피클( pickle )이 있을 수 있겠죠.


이런 재료를 갖고 샌드위치를 만들어 보겠습니다.

1) 그냥 빵,

2) 양상추가 있는 빵,

3) 피클이 있는 빵


public class Sandwich {
public void make(){
System.out.println("빵 추가");
}
}
public class SandwichWithLettuce extends Sandwich{
public void make(){
super.make();
addLettuce();
}

private void addLettuce(){
System.out.println(" + 양상추");
}
}
public class SandwichWithPickle extends Sandwich{
public void make(){
super.make();
addPickle();
}

private void addPickle(){
System.out.println(" + 피클");
}
}
public class Client {
public static void main(String args[]){
Sandwich sandwich = new Sandwich();
sandwich.make();
System.out.println("-------");

SandwichWithLettuce sandwichWithLettuce = new SandwichWithLettuce();
sandwichWithLettuce.make();
System.out.println("-------");

SandwichWithPickle sandwichWithPickle = new SandwichWithPickle();
sandwichWithPickle.make();
}
}


그런데 양상추와 피클이 모두 들어가 있는 샌드위치를 만드려면 어떻게 해야할까요?

아마도 SandwichWithLettuceAndPickle 클래스를 만들어야 할 것입니다. 

public class SandwichWithLettuceAndPickle extends Sandwich{
public void make(){
super.make();
addLettuce();
addPickle();
}

private void addLettuce(){
System.out.println(" + 양상추");
}

private void addPickle(){
System.out.println(" + 피클");
}
}


또한 치즈같은 토핑을 추가하고자 한다면, SandwichWithCheese 클래스를 만들어야 할 것이고,

여러 토핑을 조합해야 한다면, SandwichWithLettuceAndCheese , SandwichWithPickleAndCheese , SandwichWithLettuceAndPickleAndCheese 같은 클래스가 추가될 수 있습니다.


야채가 10개가 넘어가면.... 조합을 따졌을 때 총 1024개의 클래스가 있어야하네요.

이는 좋은 방법 같진 않습니다.

따라서 서브클래스를 만드는 방식이 아닌, 데커레이터 패턴을 적용하여 이를 해결해보도록 하겠습니다.





2. 데커레이터 패턴 적용

1)

먼저 Sandwich 추상클래스를 정의합니다.

양상추 샌드위치, 피클 샌드위치 등 여러 샌드위치 만드는 것을 캡슐화 하기 위해서입니다.

public abstract class Sandwich {
public abstract void make();
}


2)

다음으로 토핑을 추가하는 ToppingDecorator 클래스를 정의합니다.

ToppingDecorator 클래스는 샌드위치를 토핑하는 것이므로, Sandwich 클래스를 상속받습니다.

public class ToppingDecorator extends Sandwich{
private Sandwich sandwich;

public ToppingDecorator(Sandwich sandwich){
this.sandwich = sandwich;
}

public void make(){
sandwich.make();
}
}


2)

다음으로 빵을 추가하기 위해 Bread 클래스를 정의합니다.

빵은 데코레이터가 아닌, 기본적으로 있어야 하는 것이므로 데커레이터로 정의하지 않았습니다.

public class Bread extends Sandwich {
public void make(){
System.out.println("빵추가");
}
}



3)

다음으로 양상추, 피클을 토핑으로 추가하기 위해 LettuceDecorator , PickleDecorator 클래스를 정의합니다.

public class LettuceDecorator extends ToppingDecorator {
public LettuceDecorator(Sandwich sandwich){
super(sandwich);
}

public void make(){
super.make();
addLettuce();
}

private void addLettuce(){
System.out.println(" + 양상추");
}
}
public class PickleDecorator extends ToppingDecorator {
public PickleDecorator(Sandwich sandwich) {
super(sandwich);
}

public void make() {
super.make();
addPickle();
}

private void addPickle() {
System.out.println(" + 피클");
}
}


4)

마지막으로 샌드위치를 만드는 Client 클래스를 작성합니다.

public class Client {
public static void main(String args[]){
// 양상추 샌드위치
Sandwich sandwichWithLettuce = new LettuceDecorator(new Bread());
sandwichWithLettuce.make();
System.out.println("-------");

// 양상추+피클 샌드위치
Sandwich sandwichWithLettuceAndPickle = new PickleDecorator(new LettuceDecorator(new Bread()));
sandwichWithLettuceAndPickle.make();
}
}

데커레이터 객체를 생성할 때, 생성자로 다시 데커레이터를 생성하고, 최종적으로 Bread 객체를 생성합니다.

토핑이 더 늘어나도 이와 같이 계속 데커레이터 객체를 생성함으로써 샌드위치를 만들 수 있습니다.







이상으로 데커레이터 패턴이 무엇인지에 대해 알아보았습니다.


Java에서도 여러 디자인 패턴을 사용하고 있는데 데커레이터 패턴을 사용하고 있는 API는 다음과 같으며, 참고하시면 좋을 것 같습니다. ( 참고링크 )


Decorator 

(recognizeable by creational methods taking an instance of sameabstract/interface type which adds additional behaviour)