개요
데코레이터 패턴은 객체에 동적으로 새로운 기능을 추가하기 위한 구조적 디자인 패턴이다. 이 패턴은 상속을 사용하지 않고도 객체의 기능을 확장할 수 있도록 해준다. 데코레이터 패턴은 원래 객체를 감싸는 방식으로 기능을 추가하며, 이 감싸는 객체를 데코레이터라고 부른다.
“겉모습은 변해도 본질은 그대로”
이 패턴을 사용하기 좋은 상황은 다음과 같습니다:
- 객체에 다양한 기능을 동적으로 추가하고 싶은 경우: * 실행 시간에 객체의 기능을 유연하게 추가하거나 변경해야 하는 경우에 적합합니다. 예를 들어, 텍스트 편집기에서 다양한 텍스트 서식(글꼴, 색상, 크기 등)을 동적으로 적용해야 하는 경우.
- 상속 대신 조합을 통해 기능 확장을 원할 때: * 상속을 사용하면 클래스가 복잡해지고, 다중 상속이 불가능한 언어에서는 여러 기능을 결합하기 어려워집니다. 데코레이터 패턴은 상속 대신 객체 조합을 통해 기능을 확장함
- 기능의 조합을 다양하게 구성해야 할 때: * 여러 기능을 조합하여 다양한 조합의 객체를 생성해야 하는 경우에 유용합니다. 예를 들어, 다양한 종류의 보고서(서식 추가, 통계 추가 등)를 생성해야 하는 경우
- 기존 클래스의 수정 없이 새로운 기능을 추가하고 싶을 때: * 기존 클래스의 코드를 수정하지 않고 새로운 기능을 추가해야 할 때 적합합니다. 이는 개방-폐쇄 원칙(OCP: Open/Closed Principle)을 준수하는 설계 방식
- 객체의 주요 기능과 추가 기능을 분리하여 관리하고 싶을 때: * 객체의 핵심 기능과 추가 기능을 분리하여 각각 독립적으로 관리하고 싶을 때 사용합니다. 이는 단일 책임 원칙(SRP: Single Responsibility Principle)을 준수하는 설계 방식
장점
- 객체의 기능을 동적으로 추가 가능: * 데코레이터 패턴을 사용하면 실행 시간에 객체에 새로운 기능을 유연하게 추가할 수 있고 이는 컴파일 시점에 기능을 확정짓지 않아도 되므로, 다양한 상황에 대처할 수 있음
- 유연한 구조: * 데코레이터를 여러 개 중첩하여 사용할 수 있으므로, 다양한 기능을 조합하여 복잡한 기능을 구현할 수 있고 이는 상속을 통한 기능 확장보다 훨씬 유연
- 개방-폐쇄 원칙 준수: * 클래스의 기존 코드를 수정하지 않고도 새로운 기능을 추가할 수 있으므로, 개방-폐쇄 원칙(OCP: Open/Closed Principle)을 준수할 수 있음
- 단일 책임 원칙 준수: * 각 데코레이터는 독립적인 기능을 추가하므로, 단일 책임 원칙(SRP: Single Responsibility Principle)을 준수할 수 있으며 이는 각 클래스가 하나의 책임만 가지도록 설계하는 원칙임
- 재사용성 증가: * 데코레이터는 여러 객체에 걸쳐 재사용될 수 있다. 예를 들어, 텍스트에 색상을 추가하는 데코레이터는 다양한 텍스트 객체에 재사용될 수 있음
- 기능의 조합 가능: * 여러 데코레이터를 조합하여 다양한 기능을 구현할 수 있으며 이는 상속을 통해 기능을 확장하는 것보다 훨씬 더 많은 조합을 가능하게 함
단점
- 많은 작은 객체 생성: * 데코레이터 패턴을 많이 사용하면 객체의 수가 증가하여 시스템의 복잡성이 증가할 수 있고 각 데코레이터는 새로운 객체를 생성하므로, 객체 관리가 어려워질 수 있음
- 복잡성 증가: * 데코레이터를 많이 중첩해서 사용하면 코드의 복잡성이 증가하고 이는 코드의 가독성을 떨어뜨리고, 유지보수를 어렵게 만들 수 있음
- 디버깅 어려움: * 데코레이터를 중첩해서 사용하면 객체의 실행 흐름을 추적하기 어려워 디버깅이 복잡해질 수 있으며 특히, 많은 데코레이터가 적용된 경우에는 어떤 데코레이터가 어떤 기능을 추가했는지 파악하기 어려울 수 있음
- 객체 식별의 어려움: * 데코레이터를 사용하면 원래 객체와 데코레이터 객체를 구분하기 어려울 수 있으며 이는 특정 기능이 어디서 추가되었는지 파악하기 어려울 수 있음
- 상속을 통한 접근 제한 문제: * 데코레이터 패턴은 상속을 사용하지 않기 때문에, 상속을 통해 접근해야 하는 protected 멤버에 접근할 수 없고 이는 데코레이터 패턴을 사용할 때, 객체의 내부 상태에 접근해야 하는 경우 문제를 일으킬 수 있음
예제 코드와 함께 설명:
// Component 인터페이스
interface Text {
String getContent();
}
// ConcreteComponent 클래스
class PlainText implements Text {
private String content;
public PlainText(String content) {
this.content = content;
}
@Override
public String getContent() {
return content;
}
}
// Decorator 클래스
abstract class TextDecorator implements Text {
protected Text decoratedText;
public TextDecorator(Text decoratedText) {
this.decoratedText = decoratedText;
}
@Override
public String getContent() {
return decoratedText.getContent();
}
}
// Bold 데코레이터 클래스
class BoldText extends TextDecorator {
public BoldText(Text decoratedText) {
super(decoratedText);
}
@Override
public String getContent() {
return "<b>" + decoratedText.getContent() + "</b>";
}
}
// Italic 데코레이터 클래스
class ItalicText extends TextDecorator {
public ItalicText(Text decoratedText) {
super(decoratedText);
}
@Override
public String getContent() {
return "<i>" + decoratedText.getContent() + "</i>";
}
}
// Underline 데코레이터 클래스
class UnderlineText extends TextDecorator {
public UnderlineText(Text decoratedText) {
super(decoratedText);
}
@Override
public String getContent() {
return "<u>" + decoratedText.getContent() + "</u>";
}
}
// FontSize 데코레이터 클래스
class FontSizeDecorator extends TextDecorator {
private int fontSize;
public FontSizeDecorator(Text decoratedText, int fontSize) {
super(decoratedText);
this.fontSize = fontSize;
}
@Override
public String getContent() {
return "<span style='font-size:" + fontSize + "px;'>" + decoratedText.getContent() + "</span>";
}
}
// TextColor 데코레이터 클래스
class TextColorDecorator extends TextDecorator {
private String color;
public TextColorDecorator(Text decoratedText, String color) {
super(decoratedText);
this.color = color;
}
@Override
public String getContent() {
return "<span style='color:" + color + ";'>" + decoratedText.getContent() + "</span>";
}
}
public class DecoratorPatternExample {
public static void main(String[] args) {
Text plainText = new PlainText("Hello, World!");
// 여러 데코레이터를 중첩하여 텍스트에 다양한 서식을 적용합니다.
Text boldText = new BoldText(plainText);
Text italicText = new ItalicText(boldText);
Text underlinedText = new UnderlineText(italicText);
Text coloredText = new TextColorDecorator(underlinedText, "blue");
Text sizedText = new FontSizeDecorator(coloredText, 16);
System.out.println("Plain Text: " + plainText.getContent());
System.out.println("Bold Text: " + boldText.getContent());
System.out.println("Italic and Bold Text: " + italicText.getContent());
System.out.println("Underlined, Italic, and Bold Text: " + underlinedText.getContent());
System.out.println("Colored, Underlined, Italic, and Bold Text: " + coloredText.getContent());
System.out.println("Sized, Colored, Underlined, Italic, and Bold Text: " + sizedText.getContent());
}
}

코드 설명
- Component 인터페이스 (Text): 모든 텍스트 객체가 구현해야 하는 getContent 메서드를 정의
- ConcreteComponent 클래스 (PlainText): 기본 텍스트 객체로, 실제 텍스트 내용을 저장하고 getContent 메서드를 구현
- Decorator 클래스 (TextDecorator): Text 인터페이스를 구현하는 추상 클래스 이 클래스는 다른 텍스트 객체를 감싸고 추가 기능을 제공할 수 있는 기본 구조를 정의
- ConcreteDecorator 클래스 (BoldText, ItalicText, UnderlineText, FontSizeDecorator, TextColorDecorator): TextDecorator를 확장하여 원래 텍스트에 다양한 서식을 추가하는 구체적인 데코레이터 각 클래스는 getContent 메서드를 오버라이드하여 텍스트에 서식을 적용
- 클라이언트 코드 (DecoratorPatternExample): 다양한 데코레이터를 조합하여 텍스트에 서식을 동적으로 추가하고, 최종 결과를 출력
관련 패턴
- 어댑터(Adapter) 패턴 * 어댑터 패턴과 데코레이터 패턴 모두 객체의 인터페이스를 변경하여 클라이언트 코드에서 사용할 수 있도록 한다. 그러나 어댑터 패턴은 인터페이스 변환에 중점을 두고, 데코레이터 패턴은 기능 추가에 중점을 둠
- 컴포지트(Composite) 패턴 * 데코레이터 패턴과 컴포지트 패턴은 모두 재귀적 구조를 사용하며 컴포지트 패턴은 객체들의 계층 구조를 만들고, 데코레이터 패턴은 객체에 추가 기능을 동적으로 추가하는 데 사용됨
- 프록시(Proxy) 패턴 * 프록시 패턴과 데코레이터 패턴은 모두 다른 객체를 감싸는 구조를 가지고 있고 프록시 패턴은 접근 제어와 관련된 문제를 해결하는 데 사용되며, 데코레이터 패턴은 기능 추가에 중점을 둠
- 체인 오브 책임(Chain of Responsibility) 패턴 * 데코레이터 패턴과 마찬가지로, 체인 오브 책임 패턴은 객체의 기능을 확장하거나 수정하는 데 사용며 두 패턴 모두 요청을 처리하는 객체들을 연결하는 구조를 사용함
- 전략(Strategy) 패턴 * 데코레이터 패턴과 전략 패턴은 둘 다 객체의 동작을 변경할 수 있고 전략 패턴은 알고리즘을 변경하는 데 중점을 두고, 데코레이터 패턴은 객체에 새로운 기능을 추가하는 데 중점을 둠
- 퍼사드(Facade) 패턴 * 퍼사드 패턴은 시스템의 복잡성을 숨기기 위해 사용되며, 데코레이터 패턴은 객체의 기능을 확장하기 위해 사용되며 퍼사드 패턴과 데코레이터 패턴을 함께 사용하여 복잡한 시스템의 인터페이스를 단순화하면서도 기능을 확장할 수 있음
- 플라이웨이트(Flyweight) 패턴 * 데코레이터 패턴과 플라이웨이트 패턴은 모두 객체의 메모리 사용을 최적화하는 데 도움을 줄 수 있고 데코레이터 패턴을 사용하여 객체의 기능을 확장하면서도 플라이웨이트 패턴을 사용하여 메모리 사용을 줄일 수 있음