[디자인패턴] 구조패턴(5) - Decorator Pattern (데코레이터 패턴)

Study/Software Architecture · 2023. 5. 26. 14:56

디자인 패턴 총정리 시리즈의 열 번째 패턴은 Decorator Pattern입니다.

 

Decorator 패턴은 런타임 시 객체의 책임(Responsibility of Object)을 동적으로 추가하거나 변경할 수 있게 해 줍니다.

 

해당 게시물에서 이러한 Decorator 패턴에 대한 간단한 개념과 사용 예시, 장/단점을 알아보겠습니다.

 

1. 개요

Decorator 패턴은 구조패턴 중 하나로써 개별 객체의 동작을 동적으로 확장하기 위해 데코레이터로 객체를 감싸면서 새로운 기능을 추가할 수 있습니다. 이러한 패턴은 코드를 수정하지 않고 객체의 기능을 확장할 수 있으므로 개방 폐쇄 원칙(OCP)을 만족시키는 설계를 할 수 있습니다.

 

해당 패턴은 Component, ConcreteComponent, Decorator, ConcreteDecorator 4가지 주요 컴포넌트로 구성되어 있습니다.

 

 

2. 예시

다음은 Decorator 패턴을 사용한 테스트 편집기 프로그램을 예로 들어보겠습니다. 해당 편집기는 굵게, 이탤릭채, 밑줄 등과 같은 다양한 텍스트 포맷 옵션을 지원할 수 있습니다. 이때 여러 가지 포맷을 하위 클래스로 만드는 대신 데코레이터 패턴을 활용할 수 있습니다.

 

#include <iostream>
#include <string>

// Component Interface
class TextComponent {
public:
    virtual std::string getText() const = 0;
};

// Concrete Component
class SimpleTextComponent : public TextComponent {
public:
    std::string getText() const override {
        return "This is a simple text.";
    }
};

// Decorator Base Class
class TextDecorator : public TextComponent {
protected:
    TextComponent* component;

public:
    TextDecorator(TextComponent* component) : component(component) {}

    std::string getText() const override {
        return component->getText();
    }
};

// Concrete Decorator
class BoldDecorator : public TextDecorator {
public:
    BoldDecorator(TextComponent* component) : TextDecorator(component) {}

    std::string getText() const override {
        std::string text = TextDecorator::getText();
        return "<b>" + text + "</b>";
    }
};

// Concrete Decorator
class ItalicDecorator : public TextDecorator {
public:
    ItalicDecorator(TextComponent* component) : TextDecorator(component) {}

    std::string getText() const override {
        std::string text = TextDecorator::getText();
        return "<i>" + text + "</i>";
    }
};

int main() {
    // Create a simple text component
    TextComponent* text = new SimpleTextComponent();

    // Apply decorators to the text component
    TextComponent* boldText = new BoldDecorator(text);
    TextComponent* italicBoldText = new ItalicDecorator(boldText);

    // Get the decorated text
    std::cout << italicBoldText->getText() << std::endl;

    // Clean up
    delete italicBoldText;
    delete boldText;
    delete text;

    return 0;
}

 

해당 예시에서는 TextComponent 인터페이스와 이를 구현하는 SimpleTextComponent 클래스를 정의합니다. 

TextDecorator는 TextComponent를 상속받고, 다시 TextDecorator를 상속받아 BoldDecorator와 ItalicDecorator 클래스를 정의합니다.

 

이러한 구조를 사용하여 text 인스턴스에 main 함수에서 보이는 것처럼 동적으로 기능을 추가할 수 있습니다.

 

3. 장점

  1. Enhance Flexibility
    : Decorator 패턴은 런타임 시 동적으로 기능을 추가하거나 수정할 수 있게 하는 유연한 접근 방식을 제공합니다.
  2. Single Responsibility
    : 각 책임을 개별 Decorator 객체로 분리함으로써 해당 패턴은 단일 책임 원칙을 실현시켜 줄 수 있습니다.
  3. Code Reusability
    : Decorator를 사용하면 다양한 기능을 조합하여 사용할 수 있으므로 과한 하위 클래스를 생성하지 않아 코드 중복을 줄이고 재사용성을 높입니다.

 

4. 단점

  1. Complexity
    : Decorator 클래스를 여러 개 쌓을수록 코드의 복잡성이 증가하고, 상호작용을 이해하기 어려워질 수 있습니다.
  2. Performance Overhead
    : Decorator 패턴은 추가적인 추상화 계층과 메서드 호출 구조를 발생시켜 성능에 부하를 약간 높일 수 있습니다.

 

5. 결론

Decorator 패턴은 소프트웨어를 개발할 때 동적으로 객체의 기능 추가 및 수정을 가능하게 합니다. 해당 패턴을 적용함으로써 객체의 기능을 보다 손쉽게 향상할 수 있고, 이때 객체의 핵심 기능을 수정하지 않아 재사용성과 유지 보수성을 높입니다. 하지만, 다양한 기능이 추가될수록 코드가 복잡해지고 객체 간의 상호작용이 엉킬 수 있으므로 주의가 필요합니다.

반응형