[디자인 패턴 - C++] 데코레이터

빵욱·2024년 6월 5일

CPP_디자인 패턴

목록 보기
1/1

데코레이터 패턴에 대해서 공부한 내용 정리.

데코레이터 패턴?

Decorator 패턴?
객체에 추가 요소를 동적으로 더해 기능을 확장하는 방법.
OCP원칙을 지키면서 기능을 추가할 수 있는 기초적인 방법이라고 한다.
=> 기존 객체에 상황에 맞게 기능을 덧붙이는 패턴이라고 할 수 있다.

OCP : 클래스는 확장에는 열려 있고 변경에는 닫혀 있어야 한다는 원칙.

구조

Component: 기본 인터페이스 (추상 클래스)
ConcreteComponent: 기본 기능을 구현하는 클래스
Decorator: Component 인터페이스를 구현하고, Component 객체를 가지고 있는 추상 클래스
ConcreteDecorator: Decorator의 구체적인 서브클래스. 기능을 확장함

간단한 구현

공부하면서 찾아보니 카페 음료를 예제로 많이 사용해서 비슷하게 구현했다.

Componet

class Beverage {
public:
    virtual ~Beverage() = default; // default : 컴파일러가 만들어준 기본 소멸사 사용.
    virtual std::string getDescription() const = 0;
    virtual double cost() const = 0;
};

Component 역할을 하는 Beverage 클래스를 만들었다.

ConcreteComponent

ConcreteComponent 클랙스 구현

class Espresso : public Beverage {
public:
    std::string getDescription() const override {
        return "Esproesso";
    }

    double cost() const override    {
        return 1.99;
    }
};

기본 기능을 구현한 ConcreteComponent다.
Espresso 말고 Americano 클래스도 만들 수 있겠다.
-> 어떤 행위를 할 때 가장 기본 베이스가 되는 클래스라고 생각하면 될 듯 하다.

Decorator

// Beverage(Component)를 구현하고 Beverage(Component) 객체를 가지고 있음.
class CondimentDecorator : public Beverage {
protected:
    Beverage* beverage;

public:
    CondimentDecorator(Beverage* b) : beverage(b) {}
    virtual ~CondimentDecorator() {
        delete beverage;
    }
};

Decorator 구현
Component를 상속받아 구현하고, Component를 멤버 변수로 가지고 있다.
Component(Beverage)를 멤버 변수로 가지고 있기 때문에 확장이 가능하다.

ConcreteDecorator

ConcreteDecorator 구현

class Milk : public CondimentDecorator {
public:
    Milk(Beverage* b) : CondimentDecorator(b) {}

    std::string getDescription() const override {
        return beverage->getDescription() + ", Milk";
    }

    double cost() const override {
        return beverage->cost() + 0.30;
    }
};

class Mocha : public CondimentDecorator {
public:
    Mocha(Beverage* b) : CondimentDecorator(b) {}

    std::string getDescription() const override {
        return beverage->getDescription() + ", Mocha";
    }

    double cost() const override {
        return beverage->cost() + 0.20;
    }
};

ConcreteDecorator를 구현했다. Moca와 Milk가 있는데 이들은 Espresso에 추가되거나(확장) 안될 수도 있는 요소들이다.

Test

Console에서 테스트.

int main()
{
    Beverage* beverage = new Espresso();
    std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;

    beverage = new Milk(beverage);
    std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;

    beverage = new Mocha(beverage);
    std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;

    delete beverage;
    return 0;
}
Beverage* beverage = new Espresso();
std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;

이 부분에서 beverageEspresso 객체를 가리키는 포인터다.
따라서 getDescription()은 "Espresso"를 반환하고, cost()는 1.99를 반환한다.

beverage = new Milk(beverage);
std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;

여기서 새로운 Milk객체를 생선한다. 그 생성자에 현재 (beverage : Espresso) 포인터를 전달한다.
그러면 Milk객체는 내부적으로 beverage를 멤버 변수로 저장하는데 이는 beverage 멤버 변수에
Espresso 객체 포인터가 저장되어 있음을 뜻한다.
=> Milk 겍체가 Espresso 를 감싸게 된 것이다.

따라서 cost() 호출 시 Milk 객체의 cost() 메서드는 내부의 beverage 객체(Espresso)의 cost() 메서드를 호출하고, 0.30을 추가한다.
결과는 2.29가 된다.

Mocha를 추가하는 과정도 같은 원리다.

팩토리, 빌더

데코레이터 패턴은 팩토리 패턴과 빌더 패턴으로 이어진다고 하는데 이 부분도 나중에 공부해서 정리하려 한다..

profile
rove drink eat

0개의 댓글