-
[디자인 패턴] 데코레이터 패턴 (Decorator Pattern)Design Patterns 2025. 1. 31. 16:37
✅ 데코레이터 패턴이란?
- 객체의 기능을 동적으로 확장할 수 있는 구조적 디자인 패턴이다.
- 기존 코드 수정 없이 객체의 행동을 변경하거나 새로운 기능을 추가할 때 유용하다.
- 상속 대신 구성(Composition)과 위임(Delegation)을 활용하여 기능을 확장하는 것이 핵심이다.
✅ 핵심 개념
1️⃣ Component
- Component: 핵심 인터페이스로서, 기능의 기본 구조를 정의한다.
- Concrete Component: Component 인터페이스를 구현하는 구체 클래스이다.
2️⃣ Decorator
- Decorator: Compoent 인터페이스를 구현하는 추상 클래스이다. 실제 기능을 수행하지 않고 기존 Component를 감싼다.
- Concrete Decorator: Decorator 클래스를 상속하여 특정 기능을 추가하는 역할을 한다. 로깅, 성능 측정, 보안, 데이터 변환 등의 기능을 구현할 수 있다.
3️⃣ 전체 구조

✅ 프록시 패턴과의 차이
- 데코레이터 패턴은 프록시 패턴과 코드 구조가 매우 비슷하지만, 의도(Intent)가 다르기 때문에 별개의 패턴으로 구분된다.

✅ 장단점
장점
- 상속보다 더 유연하게 기능을 추가할 수 있다.
- OCP(Open-Closed Principle) 준수: 기존 코드 변경 없이 새로운 기능 추가할 수 있음
- SRP(Single Responsibility Principle) 준수: 기능별로 클래스를 분리하여 유지보수성을 높일 수 있음
단점
- 데코레이터 객체가 늘어날수록 복잡성이 증가하여 디버깅이 어려울 수 있다.
✅ 예제 코드: 햄버거 주문 🍔
햄버거 주문 시스템에 데코레이터 패턴을 적용하여 추가 옵션(사이드 메뉴, 토핑 등)을 동적으로 추가하는 방식을 만들어보자.
👨🏻🍳 요구사항
- Food 인터페이스를 기반으로 주문 가능한 음식(BasicFood)을 정의
- 특정 음식에 추가 옵션(치즈 추가, 감자튀김 추가, 음료 추가 등)을 동적으로 적용할 수 있어야 함
- 기존 BasicFood 클래스를 수정하지 않고 기능을 확장해야 함
- 클라이언트인 Customer 클래스의 음식을 소비하는 로직이 수정되지 않아야 함
1️⃣ Component
// 음식 (Component) public interface Food { String getDescription(); // 음식의 설명을 반환 double getCost(); // 음식의 가격을 반환 }2️⃣ Concrete Component
// 기본 음식(Concrete Component) public class BasicFood implements Food { @Override public String getDescription() { return "🍔 기본 버거"; } @Override public double getCost() { return 5000; } }3️⃣ Decorator
// 데코레이터 부모 클래스 (Decorator) public abstract class FoodDecorator implements Food { protected Food decoratedFood; public FoodDecorator(Food food) { this.decoratedFood = food; } @Override public String getDescription() { return decoratedFood.getDescription(); } @Override public double getCost() { return decoratedFood.getCost(); } }4️⃣ Concrete Decorators
- 각각 치즈, 감자튀김, 음료를 추가하는 기능을 가진 Decorator들을 만들었다.
// 치즈 추가 (Concrete Decorator) public class CheeseDecorator extends FoodDecorator { public CheeseDecorator(Food food) { super(food); } @Override public String getDescription() { return super.getDescription() + " 🧀 + 치즈 추가"; } @Override public double getCost() { return super.getCost() + 1000; } }// 감자튀김 추가 (Concrete Decorator) public class FriesDecorator extends FoodDecorator { public FriesDecorator(Food food) { super(food); } @Override public String getDescription() { return super.getDescription() + " 🍟 + 감자튀김 추가"; } @Override public double getCost() { return super.getCost() + 2000; } }// 음료 추가 (Concrete Decorator) public class DrinkDecorator extends FoodDecorator { public DrinkDecorator(Food food) { super(food); } @Override public String getDescription() { return super.getDescription() + " 🥤 + 음료 추가"; } @Override public double getCost() { return super.getCost() + 1500; } }5️⃣ Client
// 음식을 소비하는 client public class Customer { private final Food food; public Customer(Food food) { this.food = food; } public void eat() { String result = food.getDescription(); double cost = food.getCost(); System.out.println("주문한 음식: " + result); System.out.println("총 가격: " + cost); } }6️⃣ 실행 예제 및 결과
public class AppMain { public static void main(String[] args) { System.out.println("🙋🏻♂️ [1] 기본 버거 주문"); basicBurgerCustomer().eat(); System.out.println("\\n🙋🏻♂️ [2] 치즈 버거 주문"); cheeseBurgerCustomer().eat(); System.out.println("\\n🙋🏻♂️ [3] 풀세트 주문"); fullSetCustomer().eat(); } public static Customer basicBurgerCustomer() { Food basicBurger = new BasicFood(); return new Customer(basicBurger); } public static Customer cheeseBurgerCustomer() { Food cheeseBurger = new CheeseDecorator(new BasicFood()); return new Customer(cheeseBurger); } public static Customer fullSetCustomer() { Food fullSet = new DrinkDecorator(new FriesDecorator(new CheeseDecorator(new BasicFood()))); return new Customer(fullSet); } }🙋🏻♂️ [1] 기본 버거 주문 주문한 음식: 🍔 기본 버거 총 가격: 5000.0 🙋🏻♂️ [2] 치즈 버거 주문 주문한 음식: 🍔 기본 버거 🧀 + 치즈 추가 총 가격: 6000.0 🙋🏻♂️ [3] 풀세트 주문 주문한 음식: 🍔 기본 버거 🧀 + 치즈 추가 🍟 + 감자튀김 추가 🥤 + 음료 추가 총 가격: 9500.0
✅ 정리
다음 코드의 실행 흐름을 호출 스택으로 나타내보고 마무리하자.
public class AppMain { public static void main(String[] args) { System.out.println("\\n🙋🏻♂️ [3] 풀세트 주문"); fullSetCustomer().eat(); } public static Customer fullSetCustomer() { Food fullSet = new DrinkDecorator(new FriesDecorator(new CheeseDecorator(new BasicFood()))); return new Customer(fullSet); } }🙋🏻♂️ [3] 풀세트 주문 주문한 음식: 🍔 기본 버거 🧀 + 치즈 추가 🍟 + 감자튀김 추가 🥤 + 음료 추가 총 가격: 9500.01️⃣ 호출 스택 생성

- Customer가 가지고 있는 Food 객체는 DrinkDecorator이다.
- Customer::eat 메서드 내부에서 DrinkDecorator::getDescription을 호출한다.
- 이어서 Decorator들은 자신의 상위 클래스의 getDescription()을 먼저 호출하기 때문에 위 그림처럼 BasicFood까지 올라가게 된다.
2️⃣ 호출 스택 반환

- BasicFood에서 반환한 값은 자신을 호출했던 곳으로 반환되게 되어, 결국 위 그림처럼 최종적인 결과를 Customer가 받게 된다.
- getCost()의 경우도 위 구조와 동일하다.
'Design Patterns' 카테고리의 다른 글
[디자인 패턴] 프록시 패턴 (Proxy Pattern) (0) 2025.01.30 [디자인 패턴] 템플릿 콜백 패턴 (Template Callback Pattern) (0) 2025.01.30 [디자인 패턴] 전략 패턴 (Strategy Pattern) (0) 2025.01.28 [디자인 패턴] 템플릿 메서드 패턴 (Template Method Pattern) (0) 2025.01.27