디자인패턴_Decorator Pattern of Structural Pattern

2021. 1. 19. 13:522021/JOB DA STUDY

데코레이터 패턴이란, 기능을 동적으로 유연하게 확장할 수 있게 해주는 패턴이다.

 

기본 기능 + "추가 기능

 

추가 가능한 기능의 종류가 많은 경우에 각 추가 기능을 Decorator 클래스로 정의한 후 필요한 Decorator 객체를 조합해 추가 기능의 조합을 설계하는 방식

 

EX) 기본 도로 표시 기능 + 차선 표시/교통량 표시/교차로 표시/단속 카메라 표시

      => 추가 기능의 모든 조합 = 13(4+6+2+1)

 

*** 기본 기능에 추가할 수 있는 많은 종류의 부가 기능에서 파생되는 다양한 조합을 동적으로 구현할 수 있는 패턴 ***

 

  • Component

- 기본기능(ConcreteComponent)와 추가기능(Decorator)의 공통 기능을 정의

- 클라이언트는 Component를 통해 실제 객체를 사용함

 

  • ConcreteComponent

- 기본 기능을 구현하는 클래스

 

  • Decorator 

- 많은 수가 존재하는 구체적인 Decorator의 공통 기능을 제공

 

  • ConcreteDecoratorA, ConcreteDecoratorB

- Decorator의 하위 클래스로 기본 기능에 추가되는 개별적인 기능을 뜻함

- ConcreteDecorator 클래스는 ConcreteComponent 객체에 대한 참조가 필요한데, 

  이는 Decorator 클래스에서 Component 클래스로의 "합성(Composition)관계"를 통해 표현

 

합성관계 (Composition Relationship)

- 생성자에서 필드에 대한 객체를 생성하는 경우
- 전체 객체의 라이프 타임과 부분 객체의 라이프 타임은 의존적이다.
- 전체 객체가 없어지면, 부분 객체도 없어진다.

 

도로 표시 방법 조합

 

네비게이션 SW에서 도로를 표시하는 기능

 

"기본 기능=도로를 간단한 선으로 표시하는 기능" + "추가 기능=네비게이션 SW에 따라 도로의 차선을 표시하는 기능"

 

  • 기본 도로 표시 클래스
public class RoadDisplay{
    public void draw() { System.out.println("기본 도로 표시"); }
}
  • 기본 도로 표시 + 차선 표시 클래스
//상속받은 draw 메서드를 Override
public class RoadDisplayWithLane extends RoadDisplay{
    public void draw() {
        //상위 클래스 RoadDisplay의 draw 메서드 호출 
        super.draw();
        //추가 = 차선 표시
        drawLane();
    }
    private void drawLane() { System.out.println("차선 표시"); }
}

  • Client
public class Client{
    public static void main(String[] args){
        ReadDisplay road = new RoadDisplay();
        //기본기능
        road.draw();

        RoadDisplay roadWithLane = new RoadDisplayWithLane();
        //기본기능 + 추가기능
        roadWithLane.draw();
    }
}

 

 

문제점

  • 추가적으로 다른 도로 표시 기능을 추가로 구현하는 경우 - 기본 도로 표시 기능 + 교통량 표시 기능

 

 

  • 기본 도로 표시 + 교통량 표시 클래스
public class RoadDisplayWithTraffic extends RoadDisplay{
    public void draw(){
        //상위 클래스 RoadDisplay 의 draw 메서드 호출
        super.draw();
        //추가적으로 교통량 표시
        drawTraffic();
    }
    private void drawTraffix() {System.out.println("교통량 표시"); }
}

경우의 수 기본 기능 (도로 표시) 차선 표시 (추가) 교통량 표시 (추가) 교차로 표시 (추가) 클래스 이름
1 O       RoadDisplay
2 O O     RoadDisplayWith
Lane
3 O   O   RoadDisplayWith
Traffic
4 O     O RoadDisplayWith
Crossing
5 O O O   RoadDisplayWith
LaneTraffic
6 O   O O RoadDisplayWith
TrafficCrossing
7 O O   O RoadDisplayWith
LaneCrossing
8 O O O O RoadDisplayWith
LaneTrafficCrossing

***상속을 통해 조합의 각 경우를 설계한다면 각 조합별로 하위 클래스 7개를 구현해야한다***

***다양한 기능의 조합을 고려해야 하는 경우, 상속을 통한 기능의 확장은 각 기능 별로 클래스를 추가해야하는 단점***

 

해결책

  • 각 추가 기능별로 개별적 클래스를 설계하고, 기능을 조합할 때 각 클래스의 객체 조합을 이용

  • Display 클래스
public abstract class Display { public abstract void draw(); }

 

  • RoadDisplay 클래스 (기본기능)
public class RoadDisplay extends Display{
    @Override
    public void draw() { Syatem.out.println("기본 도로 표시"); }
}

 

  • DisplayDecorator 클래스 (추가기능)
public class RoadDisplayDecorator extends Display {
    private Display decoratedDisplay;

    //합성 관계를 통해 RoadDisplay 객체에 대한 참조
    public DisplayDecorator(Display decoratedDisplay){
        this.decoratedDisplaay = decoratedDisplay;
    }

    @Override
    public void draw() { decoratedDisplay.draw(); }
}

 

  • LaneDecorator, TrafficDecorator 클래스
//차선 표시 추가 클래스
public class LaneDecorator extends DisplayDecorator{
    //기존 표시 클래스 설정
    public LaneDecorator(Display decoratedDisplay) { super(decoratedDisplay); }
    @Override
    public void draw(){
        super.draw(); //설정된 기존 표시 기능
        drawLane(); // 추가된 차선 표시 기능
    }

    private void drawLane() {System.out.println("차선 표시"); }
}

//교통량 표시 추가 클래스
public class TrafficDecorator extends DisplayDecorator{
    //기존 표시 클래스 설정
    public TrafficDecorator(Display decoratedDisplay) { super(decoratedDisplay) }
    @Override
    public void draw(){
        super.draw(); //설정된 기존 표시 기능
        drawTraffic(); // 추가된  교통량 표시 기능
    }
    
    private void drawTraffic() { System.out.println("교통량 표시"); }
}

 

  • Client
public class Client{
    public static void main(String[] args){
        Display road = new RoadDisplay();
        road.draw();

    Display roadWithLane = new LaneDecorator(new RoadDisplay());
    roadWithLane.draw();

    Display roadWithTraffic = new TrafficDecorator(new RoadDisplay());
    roadWithTraffic.draw();
    }
}

road, raodWithLane, roadWithTraffic 객체의 접근은 모두 Display 클래스를 통해 이루어진다.

따라서 어떤 기능을 추가하느냐에 관계없이 Client 클래스는 동일한 Display 클래스만을 통해 일관성 있는 방식으로 도로 정보를 표시할 수 있다.

 

이처럼 Decorator 패턴을 이용하면 추가 기능 조합별로 별도의 클래스를 구현하는 대신

각 추가 기능에 해당하는 클래스의 객체를 조합해 추가 기능의 조합을 구현할 수 있게 된다.

 

 

 

 

추가
  • 기본 도로 표시 + 차선 표시 + 교통량 표시
public class Client{
    public static void main(String[] args){
    
    Display roadWithLaneAndTraffic = new TrafficDecorator(new LaneDecorator(new RoadDisplay()));
    roadWithLaneAndTraffic.draw();
    }
}
  1. 가장 먼저 생성된 RoadDisplay객체의 draw 메서드 실행
  2. 첫 번째 기능인 LaneDecorator 클래스의 drawLane 메서드 실행
  3. 두 번째 기능인 TrafficDecorator 클래스의 drawTraffic 메서드 실행
  • 교차로를 표시하는 추가 기능(CrossingDecorator)을 지원하면서, 기존의 다른 추가 기능(차선 표시 + 교통량 표시)과의 조합을 지원
public class CrossingDecorator extends DisplayDecorator {
    //기존 표시 클래스 설정
    public CrossingDecorator(Display decoratedDisplay) { super(decoratedDisplay); }
 
    @Override
    public void draw(){
        super.draw(); //설정된 기존 표시 기능을 수행
        drawCrossing(); //추가적으로 교차로 표시
        }
    //교차로 표시 기능만 직접 제공
    private void drawCrossing() {System.out.println() {System.out.println("교차로 표시"); }
}

 

  • Client에서의 사용
public class Client{
    public static void main(String[] args) {

        // (기본 도로 표시 + 차선 표시 + 교통량 표시) + 교차로 표시
        Display roadWithCrossingLaneAndTraffic = new LaneDecorator( new TrafficDecorator( new         CrossingDecorator( new RoadDisplay())));

        roadWithCrossingLaneAndTraffic.draw();
        }
    }

CrossingDecorator를 DisplayDecorator의 하위 클래스로 설계하는데, 

CrossingDecorator의 draw 메서드가 호출되면 우선 상위 클래스 DisplayDecorator의 draw 메서드를 호출한 후 CrossingDecorator의 drawCrossing 메서드를 호출한다.

 

  1. 가장 먼저 생성된 RoadDisplay 객체의 draw 메서드가 실행
  2. 첫 번째 추가 기능인 CrossingDecorator 클래스의 drawCrossing 메서드가 실행
  3. 두 번째 추가 기능인 TrafficDecorator 클래스의 drawTraffic 메서드가 실행
  4. 세 번째 추가 기능인 LaneDecorator 클래스의 drawLane 메서드가 실행