본문 바로가기
디자인패턴

[디자인패턴] 데코레이터 패턴(Decorator pattern)

by 햄과함께 2019. 9. 11.
320x100

 

데코레이터 패턴이란 기본(공통) 기능에 여러가지 기능을 조합해서 제공하고자 할 때 유용하게 사용된다.

 

만약 기능의 조합들을 상속으로만 표현하자면 조합별로 클래스를 만들어야 한다.

즉, 기본 기능에 A, B, C 기능을 추가하고자 한다면

1. 기본 기능

2. 기본 기능 + A

3. 기본 기능 + B

4. 기본 기능 + C

5. 기본 기능 + A + B

6. 기본 기능 + B + C

7. 기본 기능 + C + A

8. 기본 기능 + A + B + C

와 같이 총 8가지 클래스를 만들어야 한다.

기능이 하나씩 추가될수록 만들어야 하는 클래스의 수는 엄청나게 증가한다.

이를 해결하기 위해 데코레이터 패턴을 사용할 수 있다.


버스 정류장 전광판으로 예를 들어보자.

기본기능(공통 기능) : 곧 도착할 버스들의 번호를 보여준다.

시간 기능(A) : 정류장을 지나는 버스들이 각각 몇 분 뒤에 올 것인지 보여준다.

혼잡도 기능(B) : 다음에 올 버스의 혼잡도를 보여준다.

 

위 기능들을 그림으로 표현하면 아래와 같다.

공통 기능
시간 기능
혼잡도 기능

시간 기능과 혼잡도 기능은 공통 기능에 기능이 추가된 것이다.

설계

공통기능(기본기능)만 사용할때는 버스정류장전광판 클래스를 이용하면 된다.

여기에 시간 기능을 추가하고 싶다면 버스정류장전광판에서 draw() 함수 호출로 기본 기능을 제공하고, 추가 기능만 직접 시간Decorator에서 제공한다. 이 때, 버스정류장전광판의 객체에 대한 참조는 전광판Decorator가 담당한다. 

위 설계를 코드로 나타내면 아래와 같다.

public abstract class 전광판 {
  public abstract void draw();
}

/*
 * 기본 기능
 */
public class 버스정류장전광판 extends 전광판 {
  public void draw() {
     곧 도착할 버스들의 번호 출력;
  }
}

public abstract class 전광판Decorator extends 전광판 {
   private 전광판 decorated전광판;

   //생성자
   public 전광판Decorator (전광판 decorated전광판) {
      this.decorated전광판 = decorated전광판;
   }

   // draw 함수 제공
   public void draw() {
      decorated전광판.draw();
   }
}

/*
 * A 기능 - 시간 기능
 */
public class 시간Decorator extends 전광판Decorator {
   // 생성시 전광판 주입.
   public 시간Decorator (전광판 decorated전광판) {
       super(decorated전광판);
   }
      
   public void draw() {
       super.draw(); // 주입받은 전광판의 draw 함수 호출
       정류장을 지나는 버스들이 각각 몇 분 뒤에 오는지 출력;
   }
}

/*
 * B 기능 - 혼잡도 기능
 */
public class 혼잡도Decorator extends 전광판Decorator {
   // 생성시 전광판 주입.
   public 혼잡도Decorator (전광판 decorated전광판) {
       super(decorated전광판);
   }
      
   public void draw() {
       super.draw(); // 주입받은 전광판의 draw 함수 호출
       다음에 올 버스의 혼잡도 출력;
   }
}
// 클라이언트에서 사용.
/* 
 * 1. 공통 기능만 사용.
 */
전광판 basic = new 버스정류장전광판();
basic.draw();

/* 
 * 2. 공통 기능 + A 기능(시간기능)
 */
전광판 time = new 시간Decorator(new 버스정류장전광판());
time.draw();

/* 
 * 3. 공통 기능 + B 기능(혼잡도기능)
 */
전광판 congestion = new 혼잡도Decorator(new 버스정류장전광판());
congestion.draw();

버스정류장 전광판, 시간 Decorator, 혼잡도 Decorator는 모두 전광판을 상속받으므로 사용자는 전광판 클래스로 구현된 클래스들을 모두 관리할수도 있다.

따라서 동적으로 기능을 추가할 수 있다.

예를 들어, 현재는 시간 기능만을 추가로 필요하지만 특정 조건을 맞추는 경우 혼잡도 기능도 추가하고 싶다.

이럴때는 아래와 같이 구현할 수 있다.

void func(bool needConjestion){
   전광판 display = new 버스정류장전광판();
   display = new 시간Decorator(display);
    
   // 혼잡도가 필요한 경우
   if(needConjestion){
      display = new 혼잡도Decorator(display); // 동적으로 추가
    }   
}

 


여기에서 기능이 더 추가된다면 어떻게 될까.

만약 밤이 되면 밝게 비춰주는 기능(C)이 추가된다고 해보자.

밤이 되면 밝아지는 버스 전광판 기능

처음에 상속으로만 이를 해결하려고 했다면

4. 기본 기능 + C

6. 기본 기능 + B + C

7. 기본 기능 + C + A

8. 기본 기능 + A + B + C

총 4가지 클래스를 추가로 구현해야 됐었다.

 

하지만 데코레이터 패턴을 사용한다면 밤이 되면 밝아지는 버스 전광판 기능만을 추가로 구현하면 한다.

추가 설계

밤에빛나는Decorator - 총 1가지 클래스를 추가로 구현.

그리고 위의 4가지 기능을 사용하려면 아래와 같이 사용하면 된다.

// 4. 기본 기능 + C 
전광판 basic = new 버스정류장전광판();
전광판 basic_c= new 밤에빛나는Decorator(basic);
// 6. 기본 기능 + B + C 
전광판 basic = new 버스정류장전광판();
전광판 basic_b = new 혼잡도Decorator(basic); 
전광판 basic_b_c= new 밤에빛나는Decorator(basic_b);
// 7. 기본 기능 + C + A 
전광판 basic = new 버스정류장전광판();
전광판 basic_c = new 밤에빛나는Decorator(basic); 
전광판 basic_c_a= new 시간Decorator(basic_c);
// 8. 기본 기능 + A + B + C
전광판 basic = new 버스정류장전광판();
전광판 basic_a = new 시간Decorator(basic);
전광판 basic_a_b = new 혼잡도Decorator(basic_a);
전광판 basic_a_b_c = new 밤에빛나는Decorator(basic_a_b);

즉, 각 기능별로 클래스를 구현하고 생성자의 파라미터로 주입받아 원하는 기능을 추가해서 원하는 클래스를 만드는 방식이다.


참고 : JAVA 객체 지향 디자인패턴

 

320x100

댓글