데코레이터(Decorator) 패턴

2024. 10. 14. 04:53Design Pattern

데코레이터 패턴(Decorator)

데코레이터 패턴(Decorator Pattern)은 객체에 동적으로 기능을 추가하거나 수정할 수 있는 디자인 패턴입니다. 상속을 사용하지 않고도 객체의 기능을 확장할 수 있다는 점에서 유용합니다. 특히 OCP(Open-Closed Principle, 개방-폐쇄 원칙)을 준수하는 패턴 중 하나로, 기존 코드를 수정하지 않고 기능을 확장할 수 있는 구조를 제공합니다.


1. 데코레이터 패턴이란?

데코레이터 패턴은 동적으로 객체에 새로운 기능을 추가할 수 있도록 설계된 패턴입니다.
이 패턴을 사용하면, 클래스의 계층 구조를 복잡하게 만들지 않고도 객체의 기능을 확장할 수 있습니다.

예시

데코레이터 패턴을 사용할 때는 여러 기능을 가진 객체가 있을 때, 해당 기능을 필요에 따라 동적으로 추가할 수 있습니다. 예를 들어, 기본 커피 클래스를 정의한 후, 우유나 설탕 등의 추가 요소를 데코레이터로써 추가하여 다양한 커피를 만들 수 있습니다.


2. 데코레이터 패턴 구조

데코레이터 패턴의 구조는 크게 네 가지 요소로 구성됩니다:

  1. Component(컴포넌트): 기본 인터페이스 또는 추상 클래스로, 기능의 기본 뼈대를 정의합니다.
  2. ConcreteComponent(구체 컴포넌트): Component의 기본 구현체로, 데코레이터가 적용되지 않은 기본 객체입니다.
  3. Decorator(데코레이터): Component를 확장하는 데코레이터의 기본 클래스로, Component 인터페이스를 구현하며 ConcreteComponent 객체를 포함합니다.
  4. ConcreteDecorator(구체 데코레이터): Decorator 클래스를 상속받아 구체적인 기능을 추가하는 역할을 합니다.

3. C#에서의 데코레이터 패턴 예제

3.1 기본 컴포넌트 정의

// Component - 음료 인터페이스
public interface IBeverage
{
    string GetDescription();
    double GetCost();
}

3.2 구체 컴포넌트 구현

// ConcreteComponent - 기본 커피 클래스
public class Coffee : IBeverage
{
    public string GetDescription() => "Basic Coffee";
    public double GetCost() => 5.0;
}

3.3 데코레이터 클래스 정의

// Decorator - 데코레이터 추상 클래스
public abstract class BeverageDecorator : IBeverage
{
    protected IBeverage _beverage;

    public BeverageDecorator(IBeverage beverage)
    {
        _beverage = beverage;
    }

    public virtual string GetDescription()
    {
        return _beverage.GetDescription();
    }

    public virtual double GetCost()
    {
        return _beverage.GetCost();
    }
}

3.4 구체 데코레이터 구현

// ConcreteDecorator - 우유 추가 데코레이터
public class MilkDecorator : BeverageDecorator
{
    public MilkDecorator(IBeverage beverage) : base(beverage) { }

    public override string GetDescription()
    {
        return _beverage.GetDescription() + ", Milk";
    }

    public override double GetCost()
    {
        return _beverage.GetCost() + 1.5;
    }
}

// ConcreteDecorator - 설탕 추가 데코레이터
public class SugarDecorator : BeverageDecorator
{
    public SugarDecorator(IBeverage beverage) : base(beverage) { }

    public override string GetDescription()
    {
        return _beverage.GetDescription() + ", Sugar";
    }

    public override double GetCost()
    {
        return _beverage.GetCost() + 0.5;
    }
}

3.5 데코레이터 패턴 사용 예시

class Program
{
    static void Main(string[] args)
    {
        // 기본 커피
        IBeverage beverage = new Coffee();
        Console.WriteLine(beverage.GetDescription() + " $" + beverage.GetCost());

        // 우유 추가
        beverage = new MilkDecorator(beverage);
        Console.WriteLine(beverage.GetDescription() + " $" + beverage.GetCost());

        // 설탕 추가
        beverage = new SugarDecorator(beverage);
        Console.WriteLine(beverage.GetDescription() + " $" + beverage.GetCost());
    }
}

출력 결과:

Basic Coffee $5.0
Basic Coffee, Milk $6.5
Basic Coffee, Milk, Sugar $7.0

 


4. 데코레이터 패턴의 장점

  1. 유연성: 상속을 사용하지 않고도 객체에 새로운 기능을 추가할 수 있습니다.
  2. OCP 원칙 준수: 기존 코드를 수정하지 않고 기능을 확장할 수 있습니다.
  3. 다양한 조합 가능: 데코레이터를 조합하여 여러 가지 새로운 기능을 동적으로 추가할 수 있습니다.

5. 데코레이터 패턴의 단점

  1. 많은 클래스: 데코레이터를 추가할 때마다 새로운 클래스를 생성해야 하므로, 관리해야 할 클래스가 많아질 수 있습니다.
  2. 복잡도 증가: 데코레이터의 중첩 사용이 많아질수록 코드의 복잡도가 높아질 수 있습니다.

6. 데코레이터 패턴의 활용 사례

데코레이터 패턴은 다양한 상황에서 사용할 수 있습니다. 그중 대표적인 예는 다음과 같습니다.

  • 그래픽 UI 요소: GUI 라이브러리에서 버튼, 창 등의 컴포넌트에 여러 속성(테두리, 그림자 등)을 추가하는 데 사용됩니다.
  • 데이터 스트림: StreamReader, BufferedReader와 같은 스트림 처리에서 데코레이터 패턴을 사용하여 입출력 작업을 확장합니다.
  • 게임 개발: 게임 캐릭터에 다양한 능력을 동적으로 추가하는 데 활용할 수 있습니다. 예를 들어, 무기, 방어구, 아이템 등을 추가하거나 제거하는 방식으로 사용됩니다.

데코레이터 패턴은 객체의 기능을 동적으로 추가할 수 있도록 하여, 코드의 유연성과 확장성을 극대화하는 데 중요한 역할을 합니다. 상속을 사용하지 않으면서도 객체의 동작을 변경할 수 있다는 점에서, 복잡한 기능이 필요한 시스템에서 유용하게 활용될 수 있습니다.

'Design Pattern' 카테고리의 다른 글

상태(State) 패턴  (0) 2024.10.14
퍼사드(Facade) 패턴  (0) 2024.10.14
어댑터(Adapter) 패턴  (1) 2024.10.12
빌더(Builder) 패턴  (0) 2024.10.10
옵저버(Observer) 패턴  (5) 2024.10.03