본문 바로가기

디자인 패턴 ( Design Pattern )

[디자인 패턴] 2. 데코레이터 패턴( Decorator Pattern )



Definition

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.


데커레이터 패턴( Decorator Pattern ) 은 기본 기능에 추가할 수 있는 종류가 많은 경우에 각 추가 기능을 Decorator  클래스로 정의한 후 필요한 Decorator 객체를 조합함으로써 추가 기능의 조합을 설계하는 방식이다.


UML Class Diagram


The classes and objects participating in this pattern are:

  • Component   (LibraryItem)
    • defines the interface for objects that can have responsibilities added to them dynamically.
  • ConcreteComponent   (Book, Video)
    • defines an object to which additional responsibilities can be attached.
  • Decorator   (Decorator)
    • maintains a reference to a Component object and defines an interface that conforms to Component's interface.
  • ConcreteDecorator   (Borrowable)
    • adds responsibilities to the component.

 위에서 말하는 기본 기능이란 Borrowable 이 될것이다. Library 에 존재하는 Item 들은 ( 여기서 Book과 Video 로 예를 듦) Borrowable 한 것들이다. 하지만 책을 빌릴때와 비디오를 빌릴때의 비즈니스 로직이 차이가 있을 것이도 단순히 빌리는( Borrowable ) 기능에 뭔가를 추가하는 경우, 그 때 Decorator 패턴을 이용하면 된다.


Structural code in C#

using System;
 
namespace DoFactory.GangOfFour.Decorator.Structural
{
  /// 
  /// MainApp startup class for Structural 
  /// Decorator Design Pattern.
  /// 
  class MainApp
  {
    /// 
    /// Entry point into console application.
    /// 
    static void Main()
    {
      // Create ConcreteComponent and two Decorators
      ConcreteComponent c = new ConcreteComponent();
      ConcreteDecoratorA d1 = new ConcreteDecoratorA();
      ConcreteDecoratorB d2 = new ConcreteDecoratorB();
 
      // Link decorators
      d1.SetComponent(c);
      d2.SetComponent(d1);
 
      d2.Operation();
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// 
  /// The 'Component' abstract class
  /// 
  abstract class Component
  {
    public abstract void Operation();
  }
 
  /// 
  /// The 'ConcreteComponent' class
  /// 
  class ConcreteComponent : Component
  {
    public override void Operation()
    {
      Console.WriteLine("ConcreteComponent.Operation()");
    }
  }
 
  /// 
  /// The 'Decorator' abstract class
  /// 
  abstract class Decorator : Component
  {
    protected Component component;
 
    public void SetComponent(Component component)
    {
      this.component = component;
    }
 
    public override void Operation()
    {
      if (component != null)
      {
        component.Operation();
      }
    }
  }
 
  /// 
  /// The 'ConcreteDecoratorA' class
  /// 
  class ConcreteDecoratorA : Decorator
  {
    public override void Operation()
    {
      base.Operation();
      Console.WriteLine("ConcreteDecoratorA.Operation()");
    }
  }
 
  /// 
  /// The 'ConcreteDecoratorB' class
  /// 
  class ConcreteDecoratorB : Decorator
  {
    public override void Operation()
    {
      base.Operation();
      AddedBehavior();
      Console.WriteLine("ConcreteDecoratorB.Operation()");
    }
 
    void AddedBehavior()
    {
    }
  }
}

ConcreteDecoratorA, ConcreteDecoratorB class 에 Operation() 메소드를 보면 base.Operation() 이외에 추가적인 기능을 메소드 내에 적을 수 있다. 이게 Decorator 패턴의 핵심 내용인것이다. 아래 예시를 통해 상세히 다시 알아보자! 

Real-world code in C#

code

using System;
using System.Collections.Generic;
 
namespace DoFactory.GangOfFour.Decorator.RealWorld
{
  /// 
  /// MainApp startup class for Real-World 
  /// Decorator Design Pattern.
  /// 
  class MainApp
  {
    /// 
    /// Entry point into console application.
    /// 
    static void Main()
    {
      // Create book
      Book book = new Book("Worley", "Inside ASP.NET", 10);
      book.Display();
 
      // Create video
      Video video = new Video("Spielberg", "Jaws", 23, 92);
      video.Display();
 
      // Make video borrowable, then borrow and display
      Console.WriteLine("\nMaking video borrowable:");
 
      Borrowable borrowvideo = new Borrowable(video);
      borrowvideo.BorrowItem("Customer #1");
      borrowvideo.BorrowItem("Customer #2");
 
      borrowvideo.Display();
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// 
  /// The 'Component' abstract class
  /// 
  abstract class LibraryItem
  {
    private int _numCopies;
 
    // Property
    public int NumCopies
    {
      get { return _numCopies; }
      set { _numCopies = value; }
    }
 
    public abstract void Display();
  }
 
  /// 
  /// The 'ConcreteComponent' class
  /// 
  class Book : LibraryItem
  {
    private string _author;
    private string _title;
 
    // Constructor
    public Book(string author, string title, int numCopies)
    {
      this._author = author;
      this._title = title;
      this.NumCopies = numCopies;
    }
 
    public override void Display()
    {
      Console.WriteLine("\nBook ------ ");
      Console.WriteLine(" Author: {0}", _author);
      Console.WriteLine(" Title: {0}", _title);
      Console.WriteLine(" # Copies: {0}", NumCopies);
    }
  }
 
  /// 
  /// The 'ConcreteComponent' class
  /// 
  class Video : LibraryItem
  {
    private string _director;
    private string _title;
    private int _playTime;
 
    // Constructor
    public Video(string director, string title,
      int numCopies, int playTime)
    {
      this._director = director;
      this._title = title;
      this.NumCopies = numCopies;
      this._playTime = playTime;
    }
 
    public override void Display()
    {
      Console.WriteLine("\nVideo ----- ");
      Console.WriteLine(" Director: {0}", _director);
      Console.WriteLine(" Title: {0}", _title);
      Console.WriteLine(" # Copies: {0}", NumCopies);
      Console.WriteLine(" Playtime: {0}\n", _playTime);
    }
  }
 
  /// 
  /// The 'Decorator' abstract class
  /// 
  abstract class Decorator : LibraryItem
  {
    protected LibraryItem libraryItem;
 
    // Constructor
    public Decorator(LibraryItem libraryItem)
    {
      this.libraryItem = libraryItem;
    }
 
    public override void Display()
    {
      libraryItem.Display();
    }
  }
 
  /// 
  /// The 'ConcreteDecorator' class
  /// 
  class Borrowable : Decorator
  {
    protected List borrowers = new List();
 
    // Constructor
    public Borrowable(LibraryItem libraryItem)
      : base(libraryItem)
    {
    }
 
    public void BorrowItem(string name)
    {
      borrowers.Add(name);
      libraryItem.NumCopies--;
    }
 
    public void ReturnItem(string name)
    {
      borrowers.Remove(name);
      libraryItem.NumCopies++;
    }
 
    public override void Display()
    {
      base.Display();
 
      foreach (string borrower in borrowers)
      {
        Console.WriteLine(" borrower: " + borrower);
      }
    }
  }
}

Borrowable 클래스의 Display() 메서드를 보면 base.Display() 메서드 이외에 추가적인 비즈니스 로직이 작성됨을 볼 수 있다. 이처럼 Decorator 패턴이란 기존의 메서드에 추가할 수 있는 로직의 종류가 많은 경우 사용하면 좋다. 지금은 예시가 Borrowable 하나라 borrower 를 표현해주는 메소드만 추가적으러 적었지만, 가령 예를 들어서 구매라는 비즈니스가 추가된다면, Buyable 이라는 클래스를 만들고, 구매자를 표현하는 메서드만 Display() 메서드에 추가하면 된다. ( 즉, OCP 에 위배 되지 않는 코드를 작성할 수 있다. )


output

Book ------
Author: Worley
Title: Inside ASP.NET
# Copies: 10

Video -----
Director: Spielberg
Title: Jaws
# Copies: 23
Playtime: 92


Making video borrowable:

Video -----
Director: Spielberg
Title: Jaws
# Copies: 21
Playtime: 92

borrower: Customer #1
borrower: Customer #2


추가 의견

자바 API 중 java.io 패키지에서 데코레이터 패턴을 사용했다고 합니다.
여기서 데코레이터 패턴의 단점을 발견할 수 있다고 하는데, 데코레이터 패턴을 이용해서 디자인을 하다 보면 잡다한 클래스들이 너무 많아진다고 합니다. 따라서 데코레이터 패턴을 사용해서 디자인 하기 경우에 본인 이외에 다른사람이 이해하기 힘든 디자인이 되고는 한다고 하네요. 복잡하기도하고...  가독성의 문제 또한 생긴다고 하니... 잘 고민해서 써야 할 것 같습니다.