본문 바로가기

Design pattern

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

기본 기능을 구현하고 그들을 동적으로 조합해서 기능의 집합을 이용할 수 있는 패턴이다.

Decorator pattern을 이해하기 위해, 도로를 보여주는 클래스를 작성해보자. 선이 없는 일반적인 도로를 보여주는 기능, 차선을 보여주는 기능, 교통정체를 보여주는 기능, 교차로를 보여주는 기능이 있다. 이러한 기능은 기본적인 도로를 보여주는 클래스를 부모 클래스로 두어서 자신의 기능을 추가하면 된다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class DisplayRoad {
    public void display(){
        System.out.println("Display road");
    }
}
public class RoadDisplayWithLane extends DisplayRoad {
 
    @Override
    public void display() {
        super.display();
        displayLane();
    }
 
    private void displayLane(){
        System.out.println("Display lane");
    }
}
public class RoadDisplayWithCrossing extends DisplayRoad {
 
    @Override
    public void display() {
        super.display();
        displayCrossing();
    }
 
    private void displayCrossing(){
        System.out.println("Display crossing");
    }
}
public class RoadDisplayWithTraffic extends DisplayRoad {
 
    @Override
    public void display() {
        super.display();
        displayTraffic();
    }
 
    private void displayTraffic(){
        System.out.println("Display traffic");
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter

 

위의 설계에는 문제점이 있다.

  1. 화면을 보여주는 것이 네비게이션이라고 생각해보자. 네비게이션은 차선만 보여주는 게 아니라, 교통상황, 교차로에 대한 정보도 같이 보여준다. 이러한 경우 RoadDisplayWithLaneTraffic 클래스를 구현해야한다. RoadDisplayWithLaneCrossing도 구현해야 할 수도 있다. 이러한 방법으로, 수 많은 조합이 발생할 수 있다.

  2. RoadDisplayWithLane의 객체를 받아서 display를 실행하면 일반 도로에 대한 정보도 같이 나온다. 그런데 Traffic에 대한 정보를 출력할 때에도 일반 도로 정도가 나온다. 두 개의 객체를 같이 실행하면 중복된 정보가 나올 수 있다.

1, 2번의 문제를 해결하기 위해서, 기본 도로를 보여주는 RoadDisplay에서 원하는 클래스들을 조합해주면 된다. 마치, 레고블럭을 맞추는 것처럼

 

그렇다면, 기본 RoadDisplay클래스에 동적으로 원하는 클래스의 형태로 바꿔주면 된다. 하지만 자바는 컴파일 타임에 이미 클래스의 형태가 정해진다. 클래스를 동적으로 확장할 수 없다. 데커레이터 패턴을 이용하려면 기본 기능에서 여러 기능들을 동적으로 조합해서 만들 수 있어야한다 아쉽게도 자바를 이용해서는 동적으로 클래스의 형태 그 자체를 바꿀수는 없다. 하지만 흉내는 낼 수 있다.

 

(python은 클래스를 동적으로 수정하는 기능을 언어적으로 지원한다)

 

정리하자면, 기본기능에 추가되는 기능들을 조합하여 붙여주는 것이다. 하지만, 자바는 클래스가 동적으로 바뀌지 않는다. 그렇지만 흉내를 내어서 비슷하게 구현할 수 있다.

 

기본 베이스가 되는 클래스를 만들어 보자. 도로를 단순히 보여주는 기능이다. 이 클래스에 여러 기능들을 붙이면 된다. 기본 베이스 클래스와 추가적인 기능들의 공통점은 무언가 display를 한다는 것이다. Display라는 추상클래스를 작성하자.

 

 

이렇게 작성하면 처음과 다른점이 딱 하나있다. 기본 베이스 클래스를 뽑은 것이다. 하지만, 이렇게 해도 DisplayRoad에 동적으로 기능을 붙일 수 없다. 동적으로 기능을 붙일려면 아래와 같은 구조를 이용하면 된다.

(사실 베이스 클래스인 RoadDisplay Display부터 먼저 붙일 필요 없다. 그냥 Display를 부모로 가지면 붙일 수 있다)

 

 

이 다이어그램을 보고 바로 이해하기 조금 어렵다. 천천히 순서대로 생각해보자.

 

Decorator는 Display의 한 종류를 멤버로 가질 수 있다(연관) + Decorator는 Display의 한 종류이다(extends) 그러므로, Decorator는 Decorator를 가질 수 있다. Decorator의 종류는 RoadDisplayWithLane, RoadDisplayWithTraffic등이 있다. 즉, Decorator가 이러한 종류를 가질 수 있다. 다시 말해, RoadDisplayWithLane는 RoadDisplayWithTraffic을 가질 수 있다.

 

이러한 방법으로, 각 Decorator는 하나의 데코레이터를 연결할 수 있다. 베이스 클래스인 RoadDisplay에 Lane와 Traffic 정보를 가지도록 동적으로 수정하려면, 연결리스트처럼 한 개씩 연결해야한다.

 

 

1
2
3
public abstract class Display {
    public abstract void display();
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
1
2
3
4
5
6
public class DisplayRoad extends Display {
    @Override
    public void display(){
        System.out.println("Display road(기본 도로)");
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
1
2
3
4
5
6
7
8
9
10
11
12
public class Decorator extends Display {
    private Display decoratedDisplay;
 
    public Decorator(Display decoratedDisplay) {
        this.decoratedDisplay = decoratedDisplay;
    }
 
    @Override
    public void display() {
        decoratedDisplay.display();
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RoadDisplayWithLane extends Decorator {
 
    public RoadDisplayWithLane(Display decoratedDisplay) {
        super(decoratedDisplay);
    }
 
    @Override
    public void display() {
        super.display();
        displayLane();
    }
 
    private void displayLane(){
        System.out.println("Display lane");
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RoadDisplayWithCrossing extends Decorator {
 
    public RoadDisplayWithCrossing(Display decoratedDisplay) {
        super(decoratedDisplay);
    }
 
    @Override
    public void display() {
        super.display();
        displayCrossing();
    }
 
    private void displayCrossing(){
        System.out.println("Display crossing");
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RoadDisplayWithTraffic extends Decorator {
 
    public RoadDisplayWithTraffic(Display decoratedDisplay) {
        super(decoratedDisplay);
    }
 
    @Override
    public void display() {
        super.display();
        displayTraffic();
    }
 
    private void displayTraffic(){
        System.out.println("Display traffic");
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 
1
2
3
4
5
6
7
8
9
public class Main {
    public static void main(String[] args) {
        Display roadDisplay = new DisplayRoad(); //기본도로
        Display roadWithLane = new RoadDisplayWithLane(roadDisplay); //기본도로 + 차선
        Display roadWithLaneTraffic = new RoadDisplayWithTraffic(roadWithLane); //가본도로 + 차선 + 트래픽
 
        roadWithLaneTraffic.display();
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
 
1
2
3
Display road(기본 도로)
Display lane
Display traffic

 

roadDisplay객체가 생성된다. 베이스 클래스이다. 이후, roadWithLane객체가 생성될 때, roadDisplay를 파라미터로 넣어준다. 그러면 decorator의 생성자(super)가 호출될 때, decorator의 멤버변수로 roadDisplay가 주입된다. 이 상황에서 roadwithLane.display()를 실행한다면, 기본 도로와 차선이 보일 것이다. 연결리스트에 비유하자면, roadWithLane가 roadDisplay를 가리키는 것과 비슷한 상황이다.

 

더 나아가 roadWithLaneTraffic객체를 생성하여 roadWithTraffic을 주입한다면, 연결 상태가 추가된다. 위의 다이어그램과 같은 모양이 된다.

 

 

참고문헌

[JAVA 객체지향 디자인 패턴, 정인상](http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788968480911&orderClick=LEA&Kc=)