IT/JAVA

🧱 데코레이터 패턴 완전 정복 - 커피 주문 예제로 배우는 디자인 패턴

밥알이 2025. 6. 1. 22:18

디자인 패턴을 처음 배우는 사람도, 실무에서 적용해보고 싶은 개발자도 쉽게 이해할 수 있도록, 데코레이터 패턴을 현실적인 예제와 함께 설명드립니다.


✅ 1. 데코레이터 패턴이란?

데코레이터(Decorator) 패턴

"기존 객체에 추가적인 기능을 런타임에 동적으로 부여할 수 있는 구조 패턴"

  • 상속을 사용하지 않고 기능을 확장할 수 있어,
  • 유연한 조합이 필요할 때 적합합니다.

🎨 2. 실생활 비유: 커피 주문 시스템

커피 = 기본 객체
추가 옵션 = 데코레이터

예를 들어, 커피를 주문할 때 다음과 같은 옵션을 붙일 수 있습니다.

  • 기본 커피: 아메리카노
  • 추가 옵션: 우유, 시럽, 휘핑 등

각 옵션은 기존 커피 객체를 감싸는 구조로, ‘싸듯이(decorate)’ 기능을 확장합니다.


📌 3. 언제 사용하나요?

상황 설명
✔️ 기능 확장이 동적으로 필요할 때 런타임에 선택적으로 기능 조합
✔️ 상속보다 조합(Composition)이 더 적합할 때 계층 구조 대신 조립식 구조 사용
✔️ 기존 코드를 건드리지 않고 기능을 추가하고 싶을 때 OCP 원칙 준수 가능

🎯 4. 예제 시나리오

  • Beverage라는 커피 인터페이스가 있습니다.
  • Americano는 기본 커피입니다.
  • Milk, Vanilla는 추가 옵션입니다.
  • 각 옵션은 기존 음료를 감싸며 기능을 더합니다.

🔧 5. 실무형 자바 예제

📁 1) 기본 컴포넌트 인터페이스

public interface Beverage {
    String getDescription();
    int getCost();
}

📁 2) 기본 커피 클래스

public class Americano implements Beverage {
    @Override
    public String getDescription() {
        return "아메리카노";
    }

    @Override
    public int getCost() {
        return 2000;
    }
}

📁 3) 추상 데코레이터

public abstract class BeverageDecorator implements Beverage {
    protected Beverage beverage;

    public BeverageDecorator(Beverage beverage) {
        this.beverage = beverage;
    }
}

📁 4) 옵션 데코레이터 (우유, 바닐라)

public class Milk extends BeverageDecorator {
    public Milk(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", 우유";
    }

    @Override
    public int getCost() {
        return beverage.getCost() + 500;
    }
}

public class Vanilla extends BeverageDecorator {
    public Vanilla(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", 바닐라";
    }

    @Override
    public int getCost() {
        return beverage.getCost() + 700;
    }
}

📁 5) 사용 예제

public class Main {
    public static void main(String[] args) {
        Beverage coffee = new Americano();         // 기본 커피
        coffee = new Milk(coffee);                 // 우유 추가
        coffee = new Vanilla(coffee);              // 바닐라 추가

        System.out.println("주문: " + coffee.getDescription());
        System.out.println("가격: " + coffee.getCost() + "원");
    }
}

✅ 실행 결과

주문: 아메리카노, 우유, 바닐라
가격: 3200원

🧠 6. 구조 요약 (UML 개념)

   [Beverage] <interface>
         ▲           ▲
  [Americano]  [BeverageDecorator]
                        ▲
            [Milk], [Vanilla], ...

💡 7. 실무 사용 사례

사례 설명
🔗 Spring FilterChain 필터 체인처럼 연결해 기능 확장
📦 BufferedInputStream 스트림을 감싸며 기능 추가
🔍 Logger wrapper 로그 객체에 기능 덧붙이기
🖼️ UI 컴포넌트 테두리, 그림자 등 시각 효과 확장

📈 8. 장점 vs 단점

✅ 장점

  • 유연한 기능 확장: 상속 없이도 다양한 조합 가능
  • OCP 원칙 준수: 기존 코드 수정 없이 기능 추가
  • 런타임 조합 가능: 상황에 따라 유연한 객체 구성

⚠️ 단점

  • 클래스 수 증가: 옵션마다 클래스가 필요
  • 복잡한 구조: 중첩이 많아질 경우 디버깅 어려움

📝 9. 요약 정리

항목 설명
🧱 이름 데코레이터(Decorator) 패턴
🎯 목적 기존 객체의 기능을 동적으로 확장
🧩 구조 Component + Decorator
🛠️ 실무 활용 스트림, 필터, 로깅, UI 효과 등

🧵 마무리 한 줄 요약

데코레이터 패턴은 “조합 가능한 기능 확장”을 가능하게 해주는 구조 패턴입니다.
상속보다 유연하고, 변경보다 확장을 추구하는 객체지향의 정수를 담고 있죠.