데코레이터 패턴이란?
데코레이터 패턴(Decorator Pattern)은 객체의 기존 코드를 수정하지 않고 확장하면서 기능을 동적으로 추가하고 싶을 때 쓰는 디자인 패턴이다. 이 패턴은 상속을 사용하는 대신 객체를 "감싸는" 방식으로, 기존 객체의 기능을 더 유연하게 확장할 수 있다.
프록시 패턴(Proxy Pattern)에서도 프록시 객체에서 실제 객체를 '감싸서' 실제 객체의 접근을 제어하거나 추가적인 기능을 수행한다.
데코레이터 패턴이 쓰이는 대표적인 경우는 입출력 스트림(I/O Stream), 압축, 암호화 등의 기존의 베이스 클래스를 그대로 사용하면서 다양한 기능을 동적으로 추가해야 할 때이다.
데코레이터 패턴 예제
메서지 기능을 수행하는 기존의 객체를 만들기 위해 필요한 인터페이스와 클래스이다.
// 기존 인터페이스
public interface Message {
String getContent();
}
// 여러 인터페이스를 (다중) 상속 가능한 기존 클래스
public class SimpleMessage implements Message {
private String content;
public SimpleMessage(String content) {
this.content = content;
}
@Override
public String getContent() {
return content;
}
}
메서지 기능에서 메서지의 내용을 가져오는 기능을 정의한 Message 인터페이스와 실제 그 기능을 구현한 SimpleMessage 클래스가 있다.
다음은 기존 객체를 변경하지 않고 동적으로 기능을 추가하기 위해서 데코레이터 패턴을 적용할 것이다.
// 인터페이스를 구현한 객체를 감싸는 데코레이터 클래스 -> 런타임에 필요에 따라 객체에 기능 추가 가능
// 기존 클래스(MessageDecorator)에 추가 기능을 '데코레이션(Decoration)' 할수 있도록 추상 클래스로 작성
public abstract class MessageDecorator implements Message {
protected Message message; // 데코레이터가 감쌀 메시지 객체
public MessageDecorator(Message message) {
this.message = message;
}
@Override
public String getContent() {
return message.getContent(); // 기본 메시지 내용을 반환
}
}
기존의 클래스 SimpleMessage는 Message 인터페이스를 구현하였기 때문에, 해당 인터페이스 타입으로 기존 클래스의 인스턴스를 참조할수 있다. MessageDecorator에서 기존 클래스의 객체를 감싸면, 런타임에 기존 객체에 기능 추가할수 있다. Message의 getContent() 메서드를 오버라이딩하여, 구현부에서 주입받은 기본 객체의 getContent()를 호출할수 있기 때문이다. 그리고 MessageDecorator 를 추상 클래스로 정의하였기 때문에, 각 하위 클래스마다 필요에 맞는 기능을 추가할수 있다.
추상 클래스 MessageDecorator를 'Decorator'라고 한다. 이 클래스를 통해 하위 클래스에서 기능을 추가('decoration')할수 있기 때문이다. 그래서 'Decorator Pattern'라고 부른다.
다음은 MessageDecorator를 상속하여 각 기능을 추가한 하위 클래스들이다. EncryptedMessage는 기존의 메서지 암호화 기능을 추가하고, CompressedMessage는 메세지 압축 기능을 추가하였다.
// 데코레이터의 하위 클래스이며 기능을 추가(데코레이션)하는 클래스 (메세지 암호화 기능 추가)
public class EncryptedMessage extends MessageDecorator {
public EncryptedMessage(Message message) {
super(message);
}
@Override
public String getContent() {
return encrypt(message.getContent()); // 암호화된 메시지 반환
}
// 런타임에 객체에 암호화 기능 추가
private String encrypt(String content) {
// 간단한 암호화 시뮬레이션 (실제로는 더 복잡한 암호화 알고리즘 사용)
return new StringBuilder(content).reverse().toString();
}
}
// 데코레이터의 하위 클래스이며 기능을 추가(데코레이션)하는 클래스 (메세지 압축 기능 추가)
public class CompressedMessage extends MessageDecorator {
public CompressedMessage(Message message) {
super(message);
}
@Override
public String getContent() {
return compress(message.getContent());
}
// 런타임에 객체에 압축 기능 추가
private String compress(String content) {
// 간단한 압축 예시: 모든 공백을 제거하여 압축
return content.replaceAll("\\s+", "");
}
}
위의 코드에서 각 서브 클래스가 기존 객체의 메서드 getContent()를 오버라이딩해서 새로운 기능을 추가하도록 구현하였다. 이 메서드에서 encrypt/compress 메서드를 통해 추가적인 기능을 동적으로 수행할수 있다.
그리하여 Client에서 EncryptedMessage/ CompressedMessage의 생성자로 기존의 SimpleMessage 객체를 주입받아서, 상속받은 상위 클래스의 필드이자, EncryptedMessage 인스턴스 내에 할당된 인스턴스 변수 message로 참조한다. 그러면 기존의 객체를 변경하지 않고 추가적인 기능을 수행할 수 있게 된다.
public class Client {
public static void main(String[] args) {
Message message = new SimpleMessage("Hello, World!");
System.out.println("Original Message: " + message.getContent());
// 데코레이터를 사용하여 메시지에 암호화 기능 추가
Message encryptedMessage = new EncryptedMessage(message);
System.out.println("Encrypted Message: " + encryptedMessage.getContent());
// 데코레이터를 사용하여 메시지에 암축 기능 추가
Message compressMessage = new CompressedMessage(message);
System.out.println("Compressed Message: " + compressMessage.getContent());
}
}
실행결과를 보면, 기존 객체의 메서지, 암호화 되어 반대로 출력된 메세지와 공백 문자가 제거되어 압축된 메서지가 출력되었다.
Original Message: Hello, World!
Encrypted Message: !dlroW ,olleH
Compressed Message: Hello,World!
이처럼 데코레이터 패턴은 기존 코드를 수정하지 않고 기능을 동적으로 확장할수 있게 된다.
데코레이터 패턴 주요 개념
주요 개념
- 컴포지션(Composition): 컴포지션은 하나의 객체가 다른 객체를 감싸고, 그 객체에 새로운 기능을 덧붙이는 것이다. 즉, 상속 대신 컴포지션을 사용하여 객체에 기능을 추가한다.
- 동적 확장: 런타임에 객체의 기능을 동적으로 확장할 수 있다. 여러 데코레이터를 중첩해 사용할 수 있어 객체의 다양한 변형이 가능하다. (위의 예시에서 메시지를 암호화한 후 압축하거나, 메서지를 압축한 후에 암호화하는 것이 가능하다.)
- 기존 코드 수정 없이 기능 추가: 기존 클래스의 코드를 수정하지 않고도 새로운 기능을 추가할 수 있기 때문에.에 OCP(개방-폐쇄 원칙)를 준수할 수 있다.
구조

- Component 인터페이스/추상 클래스: 보통 인터페이스로, 기본 기능을 나타내며, 기본 객체와 데코레이터는 해당 인터페이스를 구현해야 한다.
- 예: Message 인터페이스
- Concrete Component: Component를 구현한 기본 기능을 제공하는 실제 객체이다.
- 예: SimpleMessage
- Decorator: 컴포넌트의 인터페이스를 구현하며, 다른 컴포넌트 객체를 감싸는 클래스이다. 이 클래스를 통해 기능을 확장할수 있게된다.
- 예: MessageDecorator
- Concrete Decorator: 데코레이터의 하위 클래스이며, 각 역할에 맞게 기능을 추가한다.
- 예: EncryptedMessage, CompressedMessage
데코레이터 패턴의 장단점
장점
- 유연한 기능 확장: 객체에 필요한 기능을 조합하여 동적으로 추가할 수 있다.
- OCP 준수: 기존 클래스를 변경하지 않고도 기능을 추가할 수 있어 개방-폐쇄 원칙을 따른다.
- 컴포넌트와 데코레이터를 조합 가능: 여러 데코레이터를 사용하여 다양한 기능을 쉽게 조합할 수 있다. 예를들어, 메시지를 암호화한 후 압축하거나, 메서지를 압축한 후에 암호화하는 것이 가능하다.
단점
- 복장성 증가: 기본적으로 클래스 수가 늘어나고 코드 구조가 복잡해질 수 있다. 그리고 여러 데코레이터를 중첩해서 사용하면 코드 구조가 더 복잡해진다. (메시지를 암호화한 후 압축 등)
- 성능 오버헤드: 데코레이터 객체에서 추가적인 호출 스택이 발생하여 성능 오버헤드가 생길 수 있다.
'Design Pattern' 카테고리의 다른 글
| [Java] 템플릿 메서드 패턴(Template Method Pattern) (0) | 2024.07.29 |
|---|---|
| [Java] 프록시 패턴 (Factory Pattern) (0) | 2024.07.18 |
| [Java] DI(Dependency Injection)와 IoC(Inversion of Control) (0) | 2024.06.30 |