데코레이터 패턴이란 런타임 시점에 객체에 동적으로 새로운 기능을 추가할 수 있게 해주는 디자인 패턴으로,
서브클래스를 만들지 않고 객체의 결합을 통해 기능을 확장할 수 있다.
데코레이터 패턴 구조
Component
- 기본 기능을 정의하는 인터페이스
- 데코레이터와 실제 객체(ConcreteComponent)가 구현해야 하는 공통 인터페이스 역할을 한다.
public interface Component {
String operation();
}
ConcreteComponent
- 기본 기능을 제공하는 실제 객체
- 데코레이터가 해당 객체를 감싸서 추가 기능을 제공
public class ConcreteComponent implements Component {
@Override
public String operation() {
System.out.println("ConcreteComponent.operation");
return "data";
}
}
Decorator
- 기능 추가가 목적이므로 스스로 존재할 수 없다. 따라서 추상 클래스로 선언
- 내부에 Component에 대한 참조를 가지고 있어, 실제 객체(또는 다른 데코레이터)에게 작업을 위임
public abstract class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public String operation() {
return component.operation(); // 작업 위임
}
}
ConcreteDecorator
- Decorator를 상속받아 추가적인 기능을 구현
- 기존 기능에 새로운 기능을 추가하는 역할
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component); // 부모 클래스(Decorator)의 생성자 호출
}
@Override
public String operation() {
String result = super.operation(); // 부모 클래스의 operation 호출 (기본 기능 수행)
String decoResult = "hello " + result; // 추가
return decoResult;
}
}
public class ConcreteDecorator2 extends Decorator {
public ConcreteDecorator2(Component component) {
super(component);
}
@Override
public String operation() {
String result = super.operation(); // 기존 기능 실행
String decoResult = result + "!!"; // 추가
return decoResult;
}
}
Client
public class Client {
private Component component;
public Client(Component component) {
this.component = component;
}
public void execute() {
String result = component.operation();
System.out.println(result);
}
}
public class Main {
public static void main(String[] args) {
Component basicComponent = new ConcreteComponent(); // 기본 기능을 제공하는 객체
// 데코레이터로 감싸기
Component concreteDecorator = new ConcreteDecorator(basicComponent);
// 데코레이터2로 감싸기
Component concreteDecorator2 = new ConcreteDecorator2(concreteDecorator);
Client client = new Client(concreteDecorator2);
client.execute();
}
}
동작 흐름은 다음과 같다.
1. ConcreteComponent 객체가 생성
2. ConcreteDecorator 객체를 생성하면서 ConcreteDecorator 생성자가 호출
- 생성자 내부에서 super(component) 를 호출하여 부모인 Decorator 클래스의 생성자를 호출하고, 내부 필드인 component에 basicComponent를 저장한다.
- → ConcreteDecorator는 basicComponent를 감싼다.
3. 마찬가지로 ConcreteDecorator2 객체를 생성하면서 ConcreteDecorator2 생성자가 호출
- 생성자 내부에서 super(component)를 호출하여 부모인 Decorator 클래스의 생성자를 호출하고, 내부 필드인 component에 concreteDecorator를 저장
4. Client 객체 생성를 생성하고 ConcreteDecorator2 객체를 생성자 매개변수로 받아 내부 필드 component에 저장
5. Client는 저장된 component(concreteDecorator2)의 operation() 메서드를 호출
-- operation() 호출 순서 --
6. ConcreteDecorator2.operation() 실행
super.operation(); // 부모인 Decorator 클래스의 operation()
// 즉 ConcreteDecorator.operation() 호출
7. ConcreteDecorator.operation() 실행
super.operation(); // 부모인 Decorator 클래스의 operation()
// 즉 ConcreteComponent.operation() 호출
8. ConcreteComponent.operation() 실행하고
- “data” 문자열을 반환
@Override
public String operation() {
System.out.println("ConcreteComponent.operation");
return "data";
}
9. “data”를 반환받고 이어서 앞에 “hello “ 문자열을 추가 후 반환
//ConcreteDecorator
public String operation() {
String result = super.operation(); // data 반환 받음
String decoResult = "hello " + result; // 추가
return decoResult;
}
10. “hello data”를 반환 받고 뒤에 “!!” 문자열을 추가 후 반환
//ConcreteDecorator2
public String operation() {
String result = super.operation(); // hello data
String decoResult = result + "!!"; // 추가
return decoResult;
}
11. client는 최종적으로 “hello data!!”를 반환받고 이를 출력
//Client
public void execute() {
String result = component.operation(); // hello data!!
System.out.println(result);
}
즉 각 데코레이터는 super.operation()으로 다음 단계의 객체에 작업을 위임한다.
감싼 순서대로 실행되므로 위 예제에선 가장 안쪽에 있는 ConcreteComponent의 기능이 먼저 실행된다.
데코레이터 패턴 장단점
장점
- 서브 클래스를 만드는 경우보다 더 유연하게 기능 확장 가능하다.
- 런타임에 동적으로 기능 변경이 가능하다.
- 각 데코레이터가 하나의 책임을 가지므로 단일 책임 원칙을 준수한다.
- 클라이언트가 인터페이스에 의존하므로 의존 관계 역전 원칙을 준수한다.
- 기능 확장 시에 데코레이터를 추가하면 되므로 개방 폐쇄 원칙을 준수한다.
단점
- 데코레이터가 많아질수록 클래스와 객체의 구조가 복잡해진다.
- 데코레이터가 여러 단계로 중첩되면 실행 흐름을 추적하기 어렵다.
데코레이터 패턴과 프록시 패턴의 차이점
특징 | 데코레이터 패턴 | 프록시 패턴 |
목적 | 객체의 기능을 동적으로 확장 | 객체에 대한 접근을 제어하거나 대리 수행 |
구조 | 객체를 감싸고, 새로운 기능을 추가 | 객체를 감싸고, 원래 객체에 접근 제어 |
사용 초점 | 기능 확장 | 접근 제어 |
유형 | 런타임에 기능 추가 | 원격, 로깅, 지연 초기화, 보안, 캐싱 등 특정 용도로 사용 |
'디자인 패턴' 카테고리의 다른 글
[Design Pattern] 싱글톤 패턴 (0) | 2025.05.30 |
---|---|
[Design Pattern] 정적 팩토리 메서드 패턴 (0) | 2025.04.14 |
[Design Pattern] 프록시 패턴 (0) | 2024.12.26 |
[Design Pattern] 템플릿 콜백 패턴 (0) | 2024.12.25 |
[Design Pattern] 전략 패턴 (0) | 2024.12.25 |