Programming Languange/디자인 패턴

디자인 패턴 (Design Patterns) - 데코레이터 패턴

타자치는 문돌이

데코레이터 패턴(Decorator Pattern)은 객체에 동적으로 새로운 기능을 추가하는 패턴이다.

커피 클래스가 있다고 하자. 커피는 우유, 설탕, 얼음, 휘핑 등 다양한 재료를 추가할 수 있다.

커피가 다양한 재료를 하나씩만 추가한다면 커피를 상속받는 자식 클래스를 생성하면 된다. 그러나 우유+설탕과 같이 여러 재료를 넣는 경우를 만든다면, 재료의 양에 따라 수많은 클래스가 필요하고, 새로운 재료를 추가하기도 어렵다.

대신 Decorator 클래스를 구현하고, 클래스를 덮는 클래스를 덮는 클래스를 만드는 식으로 여러 재료를 중첩할 수 있다.

구현

데코레이터 패턴은

  • Component
  • Decorator
  • Concrete Decorator
    로 구성된다.

Component

Component는 기본이 되는 클래스로, 실제 기능을 제공하는 기본 객체이다.

// Component Interface
class Coffee
{
public:
    virtual std::string getDescription() const = 0;
    virtual double cost() const = 0;
};

// Concrete Component
class SimpleCoffee : public Coffee
{
public:
    std::string getDescription() const override
    {
    return "Simple Coffee";
    }
    double cost() const override
    {
    return 1.0; // 기본 커피의 가격
    }
};

Decorator Class

Decorator Class는 Concrete Decorator Class의 기반으로, 기본 객체를 감싸고 추가 기능을 제공한다.

// Decorator
class CoffeeDecorator : public Coffee
{
protected:
    Coffee* decoratedCoffee;
public:
    CoffeeDecorator(Coffee* coffee) : decoratedCoffee(coffee) {}
    std::string getDescription() const override
    {
    return decoratedCoffee->getDescription();
    }
    double cost() const override
    {
    return decoratedCoffee->cost();
    }
};

Concrete Decorator Class

실제 추가 기능을 제공하는 클래스이다.

// ConcreteDecorator
class MilkDecorator : public CoffeeDecorator
{
public:
    MilkDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}
    std::string getDescription() const override
    {
    return CoffeeDecorator::getDescription() + ", Milk";
    }
    double cost() const override
    {
    return CoffeeDecorator::cost() + 0.5; // 우유 추가 비용
    }
};

class SugarDecorator : public CoffeeDecorator
{
public:
    SugarDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}
    std::string getDescription() const override
    {
    return CoffeeDecorator::getDescription() + ", Sugar";
    }
    double cost() const override
    {
    return CoffeeDecorator::cost() + 0.2; // 설탕 추가 비용
    }
};

예시

int main()
{
    // 기본 커피
    Coffee* myCoffee = new SimpleCoffee();
    std::cout << myCoffee->getDescription() << ", Cost: $" << myCoffee->cost() << std::endl;

    // 우유 추가
    myCoffee = new MilkDecorator(myCoffee);
    std::cout << myCoffee->getDescription() << ", Cost: $" << myCoffee->cost() << std::endl;

    // 설탕 추가
    myCoffee = new SugarDecorator(myCoffee);
    std::cout << myCoffee->getDescription() << ", Cost: $" << myCoffee->cost() << std::endl;

    ...

    return 0;
}

장점

  • 새 자식 클래스를 만들지 않고도 객체의 행동을 확장 가능하다.
  • 런타임에 객체에 책임을 추가하거나 제거할 수 있다.
  • 객체의 여러 행동을 합성할 수 있다.
  • 단일 책임 원칙에 부합한다.

단점

  • 래퍼의 스택에서 특정 래퍼를 제거하기 어렵다.
  • 스택의 순서에 의존하게 된다.
  • 코드가 보기 어렵다.

 

디자인 패턴 (Design Patterns) - Index