개발/개발 공통

[Design Pattern] Composite 패턴

growing-dev 2024. 2. 23. 14:24
반응형

Composite 패턴

구조 패턴 (Structural Pattern) 중 하나입니다.
Composite 패턴은 클라이언트로 하려면 객별 객체와 복합 객체를 모두 동일하게 다룰 수 있도록 합니다.

composite 패턴, 출처 : 위키백과

 

설명

이 패턴의 핵심 아이디어는 개별 객체와 복합 객체(Composite)를 동일한 인터페이스를 갖도록 설계하여 사용하는 것입니다. 이렇게 하면 클라이언트 코드가 개별 객체든 복합 객체든 동일한 방식으로 다룰 수 있게 됩니다.
대표적인 예제로 메뉴를 선택하는 상황을 생각해 볼 수 있습니다. 메뉴의 종류가 많은데, 하위 메뉴가 하위 메뉴를 포함하는 경우들이 있습니다. 예를 들어 분식집 메뉴가 아래와 같다고 생각해 봅니다.
참치김밥, 치즈김밥, 라면, 떡볶이
그러면 아래와 같이 카테고리를 나눌 수 있고, 오늘의 메뉴와 같이 김밥메뉴 중 하나를 선택하는 상황이 있을 수 있습니다.
김밥메뉴 : 참치김밥, 치즈김밥
오늘의메뉴 : 김밥메뉴, 라면
따라서 공통의 특징을 가진 BaseMenu를 만들어서 여러 메뉴가 생성될 때 활용할 수 있는 개념입니다. (Composite 패턴)
 

목적

Composite 패턴은 아래와 같은 상황에서 유용하게 적용될 수 있습니다. 

  1. 계층 구조를 표현해야 할 때: 객체들이 부분-전체의 계층 구조를 갖고 있고, 이를 일관되게 다루고자 할 때 Composite 패턴을 사용할 수 있습니다. 예를 들어, 그래픽 요소들의 계층 구조, 조직도, 파일 시스템 등이 이에 해당합니다.
  2. 클라이언트 코드를 단순화하고 일반화할 때: 클라이언트 코드가 개별 객체와 복합 객체를 구별하지 않고 동일한 방식으로 다룰 수 있도록 할 때 유용합니다. 이로써 클라이언트 코드가 단순해지며 일반화된 인터페이스를 통해 객체들을 조작할 수 있습니다.
  3. 재귀적인 구조를 처리해야 할 때: Composite 패턴은 재귀적인 구조를 처리하기에 적합합니다. 예를 들어, 디렉토리 안에 디렉토리가 중첩되어 있는 파일 시스템을 모델링할 때 이 패턴을 활용할 수 있습니다.
  4. 구조를 변경하지 않고도 새로운 요소를 추가하고자 할 때: Composite 패턴은 새로운 요소를 추가할 때 기존의 구조를 변경하지 않고도 쉽게 처리할 수 있습니다. 즉, 새로운 Leaf나 Composite 객체를 추가하는 것이 비교적 간단하며, 기존의 구조에 영향을 주지 않습니다.
  5. 일괄 처리(Uniform Treatment)가 필요한 경우: Composite 패턴을 사용하면 모든 객체를 일괄 처리할 수 있습니다. 즉, 모든 객체가 동일한 방식으로 다뤄질 수 있어서 코드의 일관성을 유지하고 오류를 줄일 수 있습니다.

 

예제

#include <iostream>
#include <vector>

// Component Interface
class FileSystemComponent {
public:
    virtual void display() const = 0;
    virtual ~FileSystemComponent() {}
};

// Leaf Class
class File : public FileSystemComponent {
private:
    std::string name;
public:
    File(std::string n) : name(n) {}
    void display() const override {
        std::cout << "File: " << name << std::endl;
    }
};

// Composite Class
class Folder : public FileSystemComponent {
private:
    std::string name;
    std::vector<FileSystemComponent*> children;
public:
    Folder(std::string n) : name(n) {}
    void add(FileSystemComponent* component) {
        children.push_back(component);
    }
    void display() const override {
        std::cout << "Folder: " << name << std::endl;
        for (const auto& child : children) {
            child->display();
        }
    }
};

int main() {
    Folder root("Root");
    File file1("File1");
    File file2("File2");
    Folder folder("Folder1");

    folder.add(&file1);
    folder.add(&file2);
    root.add(&folder);

    root.display();

    return 0;
}

 
위 코드에서 FileSystemComponent는 Component 인터페이스를 나타내며, File과 Folder는 각각 Leaf 클래스와 Composite 클래스입니다. Folder는 FileSystemComponent를 상속하고, 자식 컴포넌트를 관리하기 위한 컨테이너를 가집니다.
이렇게 함으로써 클라이언트는 FileSystemComponent를 통해 개별 파일 또는 폴더를 모두 동일하게 처리할 수 있습니다. 예제에서 root.display()를 호출하면 root 폴더 내의 모든 파일과 하위 폴더가 재귀적으로 표시됩니다. 이것이 Composite 패턴이 객체들을 트리 구조로 구성하여 부분-전체 계층 구조를 표현하는 방식입니다.
 

결론

Composite 패턴은 결국 계층구조로 표현될 수 있는 예제를 클라이언트에서  개별 객체와 복합 객체를 동일한 인터페이스로 다루기 위해 활용됩니다.

반응형