구조패턴 중 하나인 데코레이터 패턴을 알아봅시다.
의도
데코레이터는 객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을 해당 객체들에 연결시키는 구조적 디자인 패턴입니다. 조금 더 쉽게 설명하자면 객체의 결합을 통해서 기능을 동적으로 유연하게 확장할 수 있게 해주는 디자인 패턴입니다.
문제
도로에 여러 기능을 조합하여 길을 표시하려고 합니다.어떤 길은 기본 도로 표시 기능만 사용하여 길을 표시하도록 하는 요구사항이 있습니다.어떤 길은 기본 도로 표시 기능에 차선을 추가하여 길을 표시하도록 하는 요구사항이 있습니다.어떤 길은 기본 도로 표시 기능에 교통량을 추가하는 요구사항이 있습니다.어떤 길은 기본 도로 표시와 차선, 교통량 모두 추가하여 길을 표시하라는 요구사항이 있습니다.
그렇다면 1. 기본 도로 표시 기능을 구현하고 2. 기본 도로 + 차선 3. 기본 도로 + 교통량 4. 기본 도로 + 차선 + 교통량1, 2, 3, 4 클래스를 모두 구현해야할까요??
이 다음에 교차로 표시 기능이 추가되어서 위 1,2,3,4 번과 조합하여 사용하고 싶다면 조합할때마다 클래스를 만들어야 하는 문제점이 생깁니다.
해결책
데코레이터 패턴을 사용해봅시다. 구조는 다음과 같습니다.
1. 컴포넌트는 래퍼들과 래핑된 객체들에 대한 공통 인터페이스(추상클래스)를 선언합니다.
public abstract class Display {
public abstract void draw();
}
위 예시로는 표시하는 행위가 공통된 기능으로 묶을 수 있습니다.
2. 구상 컴포넌트는 래핑되는 객체들의 클래스이며, 기본 행동들을 정의하고 해당 기본 행동들은 데코레이터들이 변경할 수 있습니다.
public class RoadDisplay extends Display{
@Override
public void draw() {
System.out.println("기본 도로 표시");
}
}
기본 도로 표시 기능을 담당하는 RoadDisplay 클래스를 만들었습니다.
3. 기초 데코레이터 클래스에는 래핑된 객체를 참조하기 위한 필드가 있습니다. 필드의 유형은 구상 컴포넌트들과 구상 데코레이터들을 모두 포함할 수 있도록 컴포넌트 인터페이스로 선언되어야 합니다. 그 후 기초 데코레이터는 모든 작업들을 래핑된 객체에 위임합니다.
public class DisplayDecorator extends Display {
private Display decoratedDisplay;
public DisplayDecorator(Display decoratedDisplay) {
this.decoratedDisplay = decoratedDisplay;
}
@Override
public void draw() {
decoratedDisplay.draw();
}
}
DisplayDecorator라는 기초 데코레이터 클래스를 선언하였습니다. 클래스 필드에는 앞서 선언한 Display 래핑된 객체를 참조하기 위한 필드가 있습니다. 기초 데코레이터는 생성자로 들어온 래핑된 객체에게 위임합니다.
4. 구상 데코레이터들은 컴포넌트들에 동적으로 추가될 수 있는 추가 행동들을 정의합니다. 구상 데코레이터들은 기초데코레이터의 메서드를 오버라이드하고 해당 행동을 부모 메서드를 호출하기 전이나 후에 실행합니다.
public class LaneDecorator extends DisplayDecorator{
public LaneDecorator(Display decoratedDisplay) {
super(decoratedDisplay);
}
@Override
public void draw() {
super.draw();
drawLane();
}
private void drawLane() {
System.out.println("\t차선 표시");
}
}
DisplayDecorator를 상속한 LaneDecorator을 구현하였습니다.
기초데코레이터인 DisplayDecorator의 draw() 메서드를 오버라이드하여 부모 메서드를 호출한 다음 추가 기능을 호출하여 기능을 추가하였습니다.
public class TrafficDecorator extends DisplayDecorator{
public TrafficDecorator(Display decoratedDisplay) {
super(decoratedDisplay);
}
@Override
public void draw() {
super.draw();
drawTraffic();
}
private void drawTraffic() {
System.out.println("\t교통량 표시");
}
}
추가적으로 교통량 기능을 추가한 TrafficDecorator 클래스를 구현하였습니다.
5. 클라이언트는 아래에 언급한 데코레이터들이 컴포넌트 인터페이스를 통해 모든 객체와 작동하는 한 컴포넌트들을 여러 계층의 데코레이터들로 래핑할 수 있습니다.
public class Client {
public static void main(String[] args) {
Display road = new RoadDisplay();
road.draw();
Display roadWithLane = new LaneDecorator(road);
roadWithLane.draw();
Display roadWithTraffic = new TrafficDecorator(road);
roadWithTraffic.draw();
Display roadWithLaneAndTraffic = new TrafficDecorator(new LaneDecorator(road));
roadWithLaneAndTraffic.draw();
}
}
처음에 RoadDisplay 클래스로 Display road를 생성합니다. 기본 도로 표시 기능만 제공합니다.
다음으로 LaneDecorator를 통해 road를 생성자로 넘겨 Lane 차선 기능을 추가합니다.
draw() 메서드를 실행하면 기본 도로 표시 + 차선 표시 가 출력됩니다.
기본 road에 TrafficDecorator를 통해 road를 생성자로 넘겨 교통량 기능을 추가합니다.
draw() 메서드를 실행하면 기본 도로 표시 + 교통량 표시 가 출력됩니다.
마지막으로 모든 기능을 decorator로 래핑하여 기능을 합쳤습니다.
draw() 메서드를 실행하면 기본 도로 표시 + 차선 표시 + 교통량 표시 가 출력됩니다.

이렇게 데코레이터 패턴을 사용하면 런타임 중 원하는 시점에 기능을 조합하여 원하는 기능을 만들 수 있는 장점이 있습니다.
반면 특정 래퍼를 제거하기 어려운 단점이 있고 데코레이터 스택 내의 순서에 의존하지 않는 방식으로 구현하기 어렵습니다.
'리팩토링 > 디자인패턴' 카테고리의 다른 글
| [디자인패턴] 컴포지트 패턴 (1) | 2023.01.11 |
|---|---|
| [디자인패턴] 상태패턴 (3) | 2023.01.01 |
| [디자인 패턴] 템플릿 메서드 패턴 (1) | 2022.12.10 |
| [디자인패턴] 퍼사드 패턴 (1) | 2022.11.26 |
| [디자인패턴] 어댑터 패턴 (1) | 2022.11.14 |