내비게이션에서 도로를 표시하는 기능을 생각해보자. 기본 도로 표시 기능을 제공하는 RoadDisplay 클래스와 기본 도로 표시에 차선을 표시하는 RoadDisplayWithLane 클래스를 설계할 수 있다. 이때 RoadDisplayWithLane 클래스도 도로 표시 기능을 제공하므로 RoadDisplay 클래스의 하위 클래스로 설계한다.
public class RoadDisplay{
public void draw(){
System.out.println("기본 도로 표시");
}
}
public clss RoadDisplayWithLane extends RoadDisplay{
public void draw(){
super.draw();//상위 클래스, RoadDisplay의 draw 호출
drawLane();//추가적으로 차선을 표시
}
private void drawLane(){
System.out.prinln("차선 표시");
}
}
RoadDisplay 클래스에는 기본 도로 표시 기능을 실행하기 위한 draw 메서드를 구현한다. RoadDisplayLane 클래스에는 기본 도로 표시뿐만 아니라 차선을 표시하려고 상속받은 draw 메서드를 오버라이드한다.
RoadDisplay 클래스를 상속받는 RoadDisplayWithTraffic 클래스를 추가하면 된다.
RoadDisplayWithTraffic의 draw 메서드는 기본 도로 표시와 교통량을 표시해야 한다.
//기본 도로 표시 + 교통량 표시
public class RoadDisplayWithTraffic extends RoadDisplay{
public void draw(){
super.draw();//상위 클래스 RoadDisplay의 draw 메서드
drawTraffic();//추가적으로 교통량 표시
}
private void drawTraffic(){
System.out.println("교통량 표시");
}
}
RoadDisplay 클래스의 하위 클래스로 기능을 추가하는 것이 적절한 설계 방법이 될 수 있다. 하지만 다양한 기능의 조합을 고려해야 하는 경우 상속을 통한 기능 확장은 각 기능별로 클래스를 추가해야 하는 단점이 있다. 이처럼 상속을 통해 조합의 각 경우를 설계하면 조합별로 각 클래스를 구현해야 하는 문제가 있다.
앞에서 본 조합 수가 늘어나는 문제를 해결할 수 있는 방법은 각 추가 기능별로 개별적인 클래스를 설계하고, 기능을 조합할 때 각 클래스의 객체 조합을 이용하면 된다.
public abstract class Display{
public abstract void draw();
}
public class RoadDisplay extends Display{
public void draw(){
System.out.println("기본 도로 표시");
}
}
public abstract class DisplayDecorator extends Display{
private Display decoratedDisplay;
public DisplayDecorator(Display decoratedDisplay){
this.decoratedDisplay=decoratedDisplay;
}
public void draw(){
decoratedDisplay.draw();
}
}
public class LaneDecorator extends DisplayDecorator{
public LaneDecorator(Display decoratedDisplay){
super(decoratedDisplay);
}
public void draw(){
super.draw();
drawLane();
}
public void drawLane(){
System.out.println("차선표시");
}
}
public class TrafficDecorator extends DisplayDecorator{
public TrafficDecorator(Display decoratedDisplay){
super(decoratedDisplay);
}
public void draw(){
super.draw();
drawTraffic();
}
private void drawTraffic(){
System.out.println("교통량 표시");
}
}
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();//기본도로표시+교통량표시
}
}
객체의 접근이 모두 Display 클래스를 통해 이루어진다.
이런 방식으로 설계하면 추가 기능 조합별로 별도의 클래스를 구현하는 대신, 각 추가 기능에 해당하는 클래스의 객체를 조합해 추가 기능의 조합을 구현할 수 있다.
예를 들어 기본 도로 표시 기능에 추가적으로 차선도 표시하고 교통량도 표시하고 싶으면 RoadDisplay 클래스의 생성자를 기본으로 두고 LaneDecorator 클래스와 TrafficDecorator 클래스의 생성자를 사용하도록 한다.
public class Client{
public static void main(String[] args){
Display roadWithLaneAndTraffic=newTrafficDecorator(new LandDecorator(new RoadDisplay()));
roadWithLaneAndTraffic.draw();
}
}
가장 먼저 생성된 RoadDisplay 객체의 draw가 실행되고, 첫번째 추가 기능인 LanDecorator의 drawLane이 샐행되고, 두번째 추가 기능인 TrafficDecorator의 drawTraffic이 실행된다.
이런 설계는 추가 기능의 수가 많을수록 효과가 크다.
데커레이터 패턴은 기본 기능에 추가할 수 있는 기능의 종류가 많은 경우에 각 추가 기능을 Decorator 클래스로 정의한 후 필요한 Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 방식이다.
데커레이터 패턴은 기본 기능에 추가할 수 있는 많은 종류의 부가 기능에서 파생되는 다양한 조랍을 동적으로 구현할 수 있는 패턴이다.
기본기능을 뜻하는 concreteComponent와 추가 기능을 뜻하는 Decorator의 공통 기능을 정의한다. 즉, 클라이언트는 Component를 통해 실제 객체를 사용한다.
기본 기능을 구현하는 클래스이다.
많은 수가 존재하는 구체적인 Decorator의 공통 기능을 제공한다.
Decorator의 하위 클래스로 기본 기능에 추가되는 개별적인 기능을 뜻한다.