[Design pattern] 2-2. 컴포지트 패턴(Composite pattern)


Study/Design Pattern  2021. 10. 27. 20:20

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


이 글은 디자인 패턴의 컴포지트 패턴(Composite pattern)에 대한 글입니다.


컴포지트 패턴, 일명 합성 패턴이라 불리는 패턴으로 하나의 클래스와 복합 클래스(즉, 리스트)를 동일한 구성을 하여 사용하는 방법입니다.

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

#pragma once
#include <stdio.h>
#include <iostream>
#include <vector>
using namespace std;
// INode 인터페이스
class INode {
public:
  // 추상 함수
  virtual void print() = 0;
  // 소멸자 추상화
  virtual ~INode() { }
};
// Node 클래스, INode를 상속
class Node : public INode {
public:
  // print 함수 재정의
  virtual void print() {
    // 콘솔 출력
    cout << "Node - print execute" << endl;
  }
};
// NodeComposite 클래스, INode를 재정의
class NodeComposite : public INode {
private:
  // 맴버 변수
  vector<INode*> _vector;
public:
  // INode 타입의 인스턴스를 입력 받는 함수
  void add(INode* node) {
    // vector에 추가한다.
    this->_vector.push_back(node);
  }
  // print 함수 재정의
  virtual void print() {
    // vector에 저장되어 있는 인스턴스의 print 함수를 실행
    for (INode* n : this->_vector) {
      // print 함수 호출
      n->print();
    }
  }
  // 소멸자
  virtual ~NodeComposite() {
    // vector에 있는 인스턴스 모두 해제
    for (INode* n : this->_vector) {
      delete n;
    }
  }
};

int main() {
  // NodeComposite 인스턴스 생성
  NodeComposite composite;
  // composite 인스턴스에 Node 인스턴스를 넣는다.
  composite.add(new Node());
  composite.add(new Node());
  composite.add(new Node());
  // print 함수 호출
  composite.print();

  return 0;
}

컴포지트 패턴의 기본적인 형태입니다. 공통된 인터페이스에 하나는 단일 실행에 대한 print함수를 재정의했고, 하나는 복수 객체에 대한 print함수를 재정의해서 실행했습니다.

그러니깐 컴포지트 클래스는 동일한 인터페이스에 상속을 받아 리스트 형식의 맴버 변수를 만들고 add 함수를 통해 같은 인터페이스를 상속한 인스턴스를 넣고 동일한 함수명이 실행되는 형태의 패턴입니다.

import java.util.ArrayList;

// INode 인터페이스
interface INode {
  // 추상 함수
  void print();
}
// INode 인터페이스를 상속받은 Node 클래스
class Node implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println("Node - print()");
  }
}
// INode 인터페이스를 상속받은 NodeComposite 클래스
class NodeComposite extends ArrayList<INode> implements INode {
  // 함수 재정의
  public void print() {
    // List에 담긴 인스턴스 취득
    for (var node : this) {
      // 동일한 이름의 함수를 실행
      node.print();
    }
  }
}
public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Composite 인스턴스 생성
    var composite = new NodeComposite();
    // Node 인스턴스 생성하고 저장
    composite.add(new Node());
    composite.add(new Node());
    composite.add(new Node());
    // 컴포지트 인스턴스의 함수 실행
    composite.print();
  }
}

List를 맴버 변수에 넣고 구현하지만, List를 상속받고 구현하는 것도 가능합니다.

개인적으로 List를 상속받는 편이 따로 add 함수나 remove 함수를 구현할 필요가 없고 편하다고 생각합니다만, 사양에 맞춰서 맴버 함수로 List의 함수를 가리거나 어댑터 패턴으로 다른 형태로 변환할 경우가 있습니다.

using System;
using System.Collections.Generic;

// INode 인터페이스
public interface INode
{
  // 추상 함수
  void Print();
}
// INode 인터페이스를 상속받은 Node1 클래스
public class Node1 : INode
{
  // 함수 재정의
  public void Print()
  {
    // 콘솔 출력
    Console.WriteLine("Node1");
  }
}
// INode 인터페이스를 상속받은 Node2 클래스
public class Node2 : INode
{
  // 함수 재정의
  public void Print()
  {
    // 콘솔 출력
    Console.WriteLine("Node2");
  }
}

// 컴포지트 패턴은 한데 묶어야 하는 특성으로 List의 특성을 상속 받고, 인터페이스의 기능을 사용한다.
public class CompositeNode : List<INode>, INode
{
  // 함수 재정의
  public void Print()
  {
    // List에 있는 인스턴스 취득
    foreach (var node in this)
    {
      // 동일한 이름의 함수를 실행
      node.Print();
    }
  }
}

class Program
{
  static void Main(string[] args)
  {
    // composite 인스턴스 생성
    var composite = new CompositeNode();
    // INode 인터페이스를 상속받은 인스턴스를 생성하고 저장
    composite.Add(new Node1());
    composite.Add(new Node2());
    // 컴포지트 인스턴스의 함수 실행
    composite.Print();
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}

꼭 하나의 클래스에 대해서만 컴포지트 패턴이 아닙니다.

INode를 상속받은 모든 인스턴스를 동일한 구조의 Composite 클래스에서 일괄적으로 실행하기 위한 목적입니다.


여기까지 디자인 패턴의 컴포지트 패턴(Composite pattern)에 대한 글이었습니다.


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