[Design pattern] 2-4. 데코레이터 패턴 (Decorator pattern)


Study/Design Pattern  2021. 10. 28. 16:16

안녕하세요. 명월입니다.


이 글은 디자인 패턴의 데코레이터 패턴(Decorator pattern)에 대한 글입니다.


데코레이터의 영어 뜻은 장식하다, 꾸미다라는 뜻입니다. 그런 의미로 데코레이터 패턴은 인터페이스에서 상속받은 클래스들의 기능을 확장하기 위한 패턴이라고 할 수 있습니다.

출처 - https://en.wikipedia.org/wiki/Decorator_pattern

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <ctime>
using namespace std;
// INode 인터페이스
class INode {
public:
  // 함수 추상화
  virtual void print() = 0;
  virtual ~INode() { }
};
// INode 인터페이스를 상속받은 클래스
class Node : public INode {
public:
  // 함수 재정의
  virtual void print() {
    // 콘솔 출력
    cout << "Node print()" << endl;
  }
};
// INode에 대한 데코레이터 추상 클래스, INode를 상속
class NodeDecorator : public INode {
private:
  // 데코레이터 맴버 변수
  INode* node;
protected:
  // 데코레이터 맴버 변수 취득
  INode* getNode() {
    // 맴버 변수 리턴
    return this->node;
  }
public:
  // 생성자
  NodeDecorator(INode* node) {
    // 맴버 변수 설정
    this->node = node;
  }
  // 소멸자
  ~NodeDecorator() {
    // 맴버 변수 메모리 삭제
    delete this->node;
  }
};
// NodeDecorator 테코레이터 추상 클래스 상속, 시간을 추가
class NodeTimeDecorator : public NodeDecorator {
public:
  // 생성자 설정
  NodeTimeDecorator(INode* node) : NodeDecorator(node) {

  }
  // 기본적으로 INode를 상속받기에 print 함수를 재정의, 기존 node->print 결과에 시간을 추가
  void print() {
    // 현재 시간 취득
    time_t t = time(nullptr); 
    // tm 클래스로 변환
    tm* now = localtime(&t);
    // 현재 시간 콘솔에 출력
    cout << (now->tm_year + 1900) << '-' << (now->tm_mon + 1) << '-' << now->tm_mday << "  ";
    // 데코레이터 맴버 변수 print 함수 출력
    NodeDecorator::getNode()->print();
  }
};
// NodeDecorator 테코레이터 추상 클래스 상속, 로그 시작과 끝을 추가
class NodeLogDecorator : public NodeDecorator {
public:
  // 생성자 설정
  NodeLogDecorator(INode* node) : NodeDecorator(node) {

  }
  // 기본적으로 INode를 상속받기에 print 함수를 재정의, 기존 node->print 결과에 전행, 후행에 log를 추가
  void print() {
    // 콘솔 출력
    cout << "*****start log******" << endl;
    // 데코레이터 맴버 변수 print 함수 출력
    NodeDecorator::getNode()->print();
    // 콘솔 출력
    cout << "*****end log******" << endl;
  }
};
// 실행 함수
int main() {
  // Node 인스턴스 생성
  INode* node = new Node();
  // NodeTimeDecorator를 추가하고 node 변수의 인스턴스 주소 변경
  node = new NodeTimeDecorator(node);
  // NodeLogDecorator를 추가하고 node 변수의 인스턴스 주소 변경
  node = new NodeLogDecorator(node);
  // node 인스턴스의 print 함수 호출
  node->print();
  // 메모리 삭제
  delete node;

  return 0;
}

위 예제에서 처음의 Node 클래스에서는 단순한 Node->print()의 출력이 있을 뿐입니다.

그런데 NodeTimeDecorator의 데코레이터을 추가하고 거기에 NodeLogDecorator의 데코레이터를 추가했습니다.

결과는 NodeLogDecorator의 print가 호출이 되고 NodeTimeDecorator의 print가 호출이 되고 최종적으로 Node 클래스의 print가 호출이 되었습니다.

// INode 인터페이스
interface INode {
  // 추상 함수
  String output();
}


// INode 인터페이스를 상속받은 Node 클래스
class Node implements INode {
  // 함수 재정의
  public String output() {
    // 값 리턴
    return "Node";
  }
}

// INode 인터페이스를 상속받은 데코레이터 추상 클래스
abstract class ANodeDecorator implements INode {
  // 맴버 변수
  private INode node;
  // 생성자
  public ANodeDecorator(INode node) {
    // 맴버 변수 설정
    this.node = node;
  }
  // 맴버 변수 리턴
  protected INode getNode() {
    // 리턴
    return this.node;
  }
}
// ANodeDecorator 데코레이터 추상 클래스를 상속
class CheckANodeDecorator extends ANodeDecorator implements INode {
  // 생성자에서 INode의 인스턴스 설정
  public CheckANodeDecorator(INode node) {
    super(node);
  }
  // 함수 재정의
  public String output() {
    // 값 리턴 - 뒤에 Decorator 라는 문자열을 추가한다.
    return super.getNode().output() + " Decorator";
  }
}

public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Node 인스턴스 생성
    INode node = new Node();
    // 반복문을 통해서 CheckANodeDecorator를 다섯번 생성
    for (int i = 0; i < 5; i++) {
      // CheckANodeDecorator 인스턴스 생성, INode 인스턴스 입력
      node = new CheckANodeDecorator(node);
    }
    // 콘솔 출력
    System.out.println(node.output());
  }
}

데코레이터 구조는 추상 클래스에서 인터페이스를 상속받고 생성자에서 상속받은 인터페이스의 인스턴스를 받습니다.

그리고 데코레이터 추상 클래스를 상속받은 클래스에서는 인터페이스의 정의를 따라 작성하면서 맴버 변수로 있는 상속받은 인터페이스의 인스턴스를 실행합니다.

그리면 데코레이터 클래스는 INode를 상속받은 모든 인스턴스의 추상화된 함수에 데이터를 추가할 수 있습니다.

using System;
// INode 인터페이스
interface INode
{
  // 추상 함수
  int output();
}

// INode 인터페이스를 상속받은 Node 클래스
class Node : INode
{
  // 맴버 변수
  private int data;
  // 생성자
  public Node(int data)
  {
    // 맴버 변수 설정
    this.data = data;
  }
  // 함수 재정의
  public int output()
  {
    // 값 리턴
    return this.data;
  }
  // ToString 재정의
  public override String ToString()
  {
    // output의 값을 출력
    return this.output().ToString();
  }
}

// INode 인터페이스를 상속받은 데코레이터 추상 클래스
abstract class ANodeDecorator : INode
{
  // 맴버 변수
  private INode node;
  // 생성자
  public ANodeDecorator(INode node)
  {
    // 맴버 변수 설정
    this.node = node;
  }
  // 맴버 변수 리턴
  protected INode getNode()
  {
    // 리턴
    return this.node;
  }
  // ToString 재정의
  public override String ToString()
  {
    // output의 값을 출력
    return this.output().ToString();
  }
  // 인터페이스의 output 함수 추상화
  public abstract int output();

}
// ANodeDecorator 데코레이터 추상 클래스를 상속
class MultiplyDecorator : ANodeDecorator, INode
{
  // 생성자에서 INode의 인스턴스 설정
  public MultiplyDecorator(INode node) : base(node) { }
  // 함수 재정의
  public override int output()
  {
    // 값 리턴 - 10의 값을 곱한다.
    return base.getNode().output() * 10;
  }
}
// ANodeDecorator 데코레이터 추상 클래스를 상속
class DivisionDecorator : ANodeDecorator, INode
{
  // 생성자에서 INode의 인스턴스 설정
  public DivisionDecorator(INode node) : base(node) { }
  // 함수 재정의
  public override int output()
  {
    // 값 리턴 - 10의 값을 나눈다.
    return base.getNode().output() / 10;
  }
}

class Program
{
  // 열거형 타입
  enum CalType
  {
    Multiply,
    Division
  }
  // 팩토리 메서드 패턴
  static INode GetNodeFactory(int data, CalType? calType = null)
  {
    // Node 인스턴스 생성
    INode node = new Node(data);
    // 파라미터에서 CalType.Multiply이 오면
    if (calType == CalType.Multiply)
    {
      // MultiplyDecorator 데코레이터로 인스턴스 생성
      node = new MultiplyDecorator(node);
    }
    // 파라미터에서 CalType.Division이 오면
    else if (calType == CalType.Division)
    {
      // MultiplyDecorator 데코레이터로 인스턴스 생성
      node = new DivisionDecorator(node);
    }
    // 인스턴스 리턴
    return node;
  }
  // 실행 함수
  static void Main(string[] args)
  {
    // 열거형 파라미터가 없이 팩토리 메서드 함수를 호출하여 INode의 값을 취득, ToString으로 콘솔 출력
    Console.WriteLine(GetNodeFactory(100));
    // 열거형 파라미터가 CalType.Multiply로 팩토리 메서드 함수를 호출하여 INode의 값을 취득, ToString으로 콘솔 출력 (MultiplyDecorator 데코레이터 인스턴스)
    Console.WriteLine(GetNodeFactory(100, CalType.Multiply));
    // 열거형 파라미터가 CalType.Division로 팩토리 메서드 함수를 호출하여 INode의 값을 취득, ToString으로 콘솔 출력 (DivisionDecorator 데코레이터 인스턴스)
    Console.WriteLine(GetNodeFactory(100, CalType.Division));

    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}

위 예제는 데코레이터 패턴에 팩토리 메서드 패턴을 추가한 것입니다.

GetNodeFactory 함수에서 CalType 타입의 파라미터의 결과에 따라 취득하는 인스턴스의 종료가 다르네요.


개인적으로 구조 패턴 중에서 파사드 패턴(Facade pattern) 다음으로 가장 많이 사용되는 패턴이지 않을까 싶네요. 예를 들면 프레임 워크나 .Net Framework에서 제공하는 기본 클래스를 사양에 맞게 바꾸는 경우가 많은데 그럴 경우 사용하면 프로그램이 굉장히 편해지는 것을 느낄 수 있습니다.

특히나 로그 처리 클래스나 기타 데이터베이스 데이터 처리 클래스에서 많이 사용하는 듯 싶네요.


여기까지 디자인 패턴의 데코레이터 패턴(Decorator pattern)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.