디자인패턴 - 데코레이터 패턴

JeongHoHyun·2025년 3월 13일

Java

목록 보기
21/23

데코레이터 패턴

데코레이터 패턴은 기존 객체에 추가적인 기능을 동적으로 부여할 수 있도록 설계하는 구조적(Structural) 디자인 패턴입니다.
이를 통해 클래스를 수정하지 않고도 객체의 기능을 확장할 수 있습니다.

주요 개념

  1. 객체의 기능을 확장하는 방법
    • 상속(Inheritance) 을 사용하면 새로운 기능을 추가할 수 있지만, 서브클래스가 많아질 수 있음.
    • 조합(Composition)을 활용하여 유연하게 기능을 추가할 수 있음.
  2. 클래스를 수정하지 않고도 객체의 동작을 변경 가능
    • OCP(Open-Closed Principle, 개방-폐쇄 윈칙) 준수
    • 기존 클래스 변경 없이 기능 추가 가능 (기존 코드 수정 없이 새로운 기능 추가)
  3. 런타임 시점에서 객체의 기능을 동적으로 조합 가능
    • 새로운 기능을 가진 객체를 쉽게 생성 가능
    • 여러 데코레이터를 조합하여 원하는 기능을 구현 가능

구조

  1. Component (기본 인터페이스 / 추상 클래스)
    • 기본 기능을 정의하는 인터페이스 또는 추상 클래스
  2. ConcreteComponent (구체적인 컴포넌트)
    • 기본 기능을 실제로 구현하는 클래스
  3. Decorator (데코레이터 추상 클래스)
    • Component를 확장하며, 추가적인 기능을 정의할 수 있도록 하는 추상 클래스
  4. ConcreteDecorator (구체적인 데코레이터)
    • 실제로 기능을 추가하는 역할을 하는 클래스

☕️ 예제 코드 (Java)

// 1. Component 인터페이스 정의
interface Coffee {
    String getDescription();
    double cost();
}

// 2. ConcreteComponent (구체적인 기본 클래스)
class BasicCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "기본 커피";
    }

    @Override
    public double cost() {
        return 3000;
    }
}

// 3. Decorator (데코레이터 추상 클래스)
abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee; // 기존 커피 객체를 감싸기 위한 변수

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return coffee.getDescription();
    }

    @Override
    public double cost() {
        return coffee.cost();
    }
}

// 4. ConcreteDecorator (구체적인 데코레이터: 우유 추가)
class Milk extends CoffeeDecorator {
    public Milk(Coffee coffee) {
        super(coffee);
    }

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

    @Override
    public double cost() {
        return coffee.cost() + 500;
    }
}

// 5. ConcreteDecorator (구체적인 데코레이터: 시럽 추가)
class Syrup extends CoffeeDecorator {
    public Syrup(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + ", 시럽 추가";
    }

    @Override
    public double cost() {
        return coffee.cost() + 300;
    }
}

// 6. 사용 예시
public class DecoratorPatternExample {
    public static void main(String[] args) {
        Coffee basicCoffee = new BasicCoffee();
        System.out.println(basicCoffee.getDescription() + " 가격: " + basicCoffee.cost() + "원");

        Coffee milkCoffee = new Milk(basicCoffee);
        System.out.println(milkCoffee.getDescription() + " 가격: " + milkCoffee.cost() + "원");

        Coffee syrupMilkCoffee = new Syrup(milkCoffee);
        System.out.println(syrupMilkCoffee.getDescription() + " 가격: " + syrupMilkCoffee.cost() + "원");
    }
}

💻 출력 결과

기본 커피 가격: 3000원
기본 커피, 우유 추가 가격: 3500원
기본 커피, 우유 추가, 시럽 추가 가격: 3800원

✅ 장점

  • 기능확장성
    • 기존 코드를 변경하지 않고도 새로운 기능을 추가 가능
  • 조합 가능
    • 여러 개의 데코레이터를 조합하여 다양한 기능을 적용 가능
  • 유연성
    • 상속보다 더 유연한 설계 가능(불필요한 서브클래스 생성 방지)
  • OCP(개방-폐쇄 원칙)준수
    • 기존 클래스 수정 없이 기능 확장 가능

❌ 단점

  • 많은 객체 생성
    • 데코레이터를 많이 사용하면 객체가 여러 개 중첩되면서 복잡도가 증가할 수 있음
  • 디버깅 어려움
    • 다층 구조로 인해 실제 실행시 어떤 객체가 호출되는지 파악하기 어려움
  • 구현 복잡성 증가
    • 단순한 기능 추가보다 코드가 더 복잡해질 수 있음

언제 사용할까?

  • 기능 추가가 자주 발생하는 경우
    • 객체에 새로운 기능을 추가할 가능성이 높을 때
  • 클래스를 변경하지 않고 기능을 확장하고 싶을 때
  • 기능 조합이 다양하게 이루어져야 할 때
  • 상속보다 유연한 구조가 필요한 경우
profile
Java Back-End 2022.11.01 💻~ing

0개의 댓글