본문 바로가기
까망 동네/디자인 패턴

[디자인패턴] 장식자 패턴 (Decorator)

by 까망 하르방 2023. 11. 1.
반응형

🎈 데코레이터 패턴 (Decorator Pattern) 

• 객체에 동적으로 기능을 추가하거나 변경할 수 있게 해주는 패턴

• 압축, 암호화, 버퍼링 기능 추가시 활용해볼 수 있다.

• 디자인 패턴 중 구조 패턴에 해당된다.

 

💻 디자인 패턴(Design Pattern)이란?

👨‍💻 디자인 패턴(Design Pattern)이란? • SW 개발 방법 중에서도 구조적인 문제 해결에 목적을 둔다. • 알고리즘과 같이 특정 문제를 해결하는 Logic 형태보다는 특정 상황에 적용할 수 있는 방

zoosso.tistory.com

 
 
[예제 코드]  데코레이터 패턴 적용 X

 

#include <iostream>

class Coffee {
public:
    void brew() {
        std::cout << "커피 내리기" << std::endl;
    }
};

void main() {
    Coffee coffee;
    coffee.brew();
}

 

데코레이터 패턴 예시 결과

 

커피를 내리는 클래스이다.

단일 클래스로 커피 동작을 정의하고 있지만

여기서 우유를 넣거나 휘핑크림을 추가로 넣는다면 어떻게 해야할까?

Coffee 클래스 내부를 변경한다면 OCP, SRP를 지키지 못하게 된다.

 

보통 객체지향에서 새로운 기능을 추가할 때, 상속을 사용한다.

하지만 상속은 상위 클래스와 하위 클래스간에 강력한 결합 관계가 되어 유연성이 떨어진다.

예를들어 상위 클래스에 기능을 추가할 때 맞춰서 override 해주어야 한다.

또한 상위 클래스에 불필요한 메서드가 있을수도 있다.

 

 

[예제 코드]  데코레이터 패턴 적용 O

#include <iostream>

class Coffee {
public:
    virtual void brew() = 0;
};

class SimpleCoffee : public Coffee {
public:
    void brew() override {
        std::cout << "커피 내리기" << std::endl;
    }
};

class CoffeeDecorator : public Coffee {
protected:
    Coffee* coffee;

public:
    CoffeeDecorator(Coffee* coffee) : coffee(coffee) {}

    void brew() override {
        coffee->brew();
    }
};

class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}

    void brew() override {
        CoffeeDecorator::brew();
        addMilk();
    }

    void addMilk() {
        std::cout << "우유 추가" << std::endl;
    }
};

void main() {
    Coffee* simpleCoffee = new SimpleCoffee();
    Coffee* coffeeWithMilk = new MilkDecorator(simpleCoffee);
    
    puts("1.");
    simpleCoffee->brew();

    puts("\n2.");
    coffeeWithMilk->brew();
}

 

데코레이터 패턴 예시 결과

 

MilkDecorator를 통해서 우류를 동적으로 추가할 수 있다.

즉, Coffer 클래스를 변경하지 않고 코드를 확장할 수 있다.


 

🎈 장식자 패턴 장단점

+ 개방 폐쇄 원칙(OCP): 기본 객체 변경없이 새로운 기능 추가 가능

+ 단일 책임 원칙(SRP): 각 데코레이터 클래스가 특정 기능을 추가하기에 클래스의 역할 분리가 잘된다.

+ 재사용: 다양한 데코레이터를 조합으로 재사용성 up

+ 동적으로 처리되는 시점에 자원을 할당 받아 사용

 

-  복잡성 증가: 클래스 수 자체는 증가할 수 밖에 없다.

-  성능 저하: 각 객체에서 추가 작업이 이뤄지기에 실행 시간에 성능저하가 있을 수 있다.

 

[예제 코드]

#include <iostream>
#include <string>

class CoffeeOrder {
public:
    virtual std::string getDescription() = 0;
    virtual double cost() = 0;
};


class SimpleCoffee : public CoffeeOrder {
public:
    std::string getDescription() {
        return "간단한 커피";
    }

    double cost() {
        return 2.0;
    }
};


class CoffeeDecorator : public CoffeeOrder {
protected:
    CoffeeOrder* decoratedCoffee;


public:
    CoffeeDecorator(CoffeeOrder* coffee) : decoratedCoffee(coffee) {}

    std::string getDescription() {
        return decoratedCoffee->getDescription();
    }

    double cost() {
        return decoratedCoffee->cost();
    }
};


class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(CoffeeOrder* coffee) : CoffeeDecorator(coffee) {}

    std::string getDescription() {
        return CoffeeDecorator::getDescription() + ", 우유";
    }

    double cost() {
        return CoffeeDecorator::cost() + 0.5;
    }
};


class WhippedCreamDecorator : public CoffeeDecorator {
public:
    WhippedCreamDecorator(CoffeeOrder* coffee) : CoffeeDecorator(coffee) {}

    std::string getDescription() {
        return CoffeeDecorator::getDescription() + ", 휘핑 크림";
    }

    double cost() {
        return CoffeeDecorator::cost() + 0.7;
    }
};

void main() {
    CoffeeOrder* simpleCoffee = new SimpleCoffee();
    std::cout << "주문: " << simpleCoffee->getDescription() << ", 가격: $" << simpleCoffee->cost() << std::endl;

    CoffeeOrder* coffeeWithMilk = new MilkDecorator(simpleCoffee);
    std::cout << "주문: " << coffeeWithMilk->getDescription() << ", 가격: $" << coffeeWithMilk->cost() << std::endl;

    CoffeeOrder* coffeeWithWhippedCream = new WhippedCreamDecorator(simpleCoffee);
    std::cout << "주문: " << coffeeWithWhippedCream->getDescription() << ", 가격: $" << coffeeWithWhippedCream->cost() << std::endl;

    CoffeeOrder* coffeeWithMilkAndWhippedCream = new WhippedCreamDecorator(new MilkDecorator(simpleCoffee));
    std::cout << "주문: " << coffeeWithMilkAndWhippedCream->getDescription() << ", 가격: $" << coffeeWithMilkAndWhippedCream->cost() << std::endl;
}

 

데코레이터 패턴 예시 결과

 

커피에서 시작하여 우유 → 휘핑크림까지

데코레이터 패턴을 사용하여 커피 주문 시스템에 동적으로 기능 추가하였다.

이를 통해 새로운 종류의 커피나 추가 옵션을 고려해볼 수 있다.


 

🎈 다른 디자인 패턴과 비교

• 어댑터 패턴

어댑터 패턴은 인터페이스를 다른 인터페이스로 변환 (호환성 제공)

장식자 패턴은 기능/행동을 변경한다.

 

 

• 컴포지트 패턴 

컴포지트 패턴은 객체를 합성하는 것에 비해

장식자 패턴은 새로운 객체 행위를 추가하는 것이다.

 

 

• 싱글턴 (Singleton)

싱글턴 패턴은 특정 클래스에서 단일 인스턴스로 관리하는데

데코레이터 패턴은 기능 확장을 위해 여러 클래스가 조합하는 형태이다.

 

[디자인패턴] 싱글턴 패턴

싱글턴(Singleton) 패턴이란? • 단, 하나의 객체를 만들어서 사용 (단일 객체) → 한번 생성되고 프로그램 종료될 때까지 메모리에 상주 • 디자인 패턴(Design Pattern)에서 "생성 패턴"에 해당 • 다른

zoosso.tistory.com

 

 

팩토리 (Factory)

팩토리 패턴은 객체 생성을 캡슐화하고,

어떤 클래스의 인스턴스를 반환하는데 사용

데코레이터 패턴은 이미 존재하는 객체를 변경/확장하는데 사용

 

팩토리 메서드 패턴 (Factory Method)

🎈 팩토리 메서드 패턴(Factory Method Pattern) • 객체 생성 시 확장을 쉽게 하기 위한 설계 방법 강력한 결합 관계는 코드의 수정•변경을 어렵게 한다. • 객체 생성 동작을 별도 클래스로 분리하

zoosso.tistory.com

 

 

• 전략 (Strategy)

전략 패턴은 알고리즘을 캡슐화하고 동적으로 교체하는 것이다.

 

💻 [디자인패턴] 전략 패턴 (Strategy Pattern)

전략 패턴 (Strategy Pattern) 이란? 전략(Strategy)은 코드 내부에서 로직(Logic)을 처리하는 「알고리즘」 어떤 목적 달성을 위한 수행 방식이라고 생각하면 좋다. 영화관에서 이벤트 영화 예매 방식을

zoosso.tistory.com

 

 

• 템플릿 메소드 (Template Method)

템플릿 메서드 패턴은 알고리즘 기본 구조를 정의하고

하위 클래스에서 구체적으로 구현하도록 하는 패턴 (for. 알고리즘 재사용)

 

💻 템플릿 메소드 (Template Method) 패턴

템플릿 메소드 패턴 (Template Method Pattern) 상위 클래스에서 먼저 전체 흐름(큰 골격)을 구현하고, 실제적인 동작은 하위 클래스에서 구현 이러한 동작은 후크 기능과 유사하다. 후크 (Hook) 하위 클

zoosso.tistory.com

반응형

댓글