만세! 한국어 버전이 드디어 출시되었습니다! 피드백을 공유하거나 오류를 보고하려면 메시지를 보내주세요.

복합체 패턴

다음 이름으로도 불립니다: 객체 트리, Composite

의도

복합체 패턴은 객체들을 트리 구조들로 구성한 후, 이러한 구조들과 개별 객체들처럼 작업할 수 있도록 하는 구조 패턴입니다.

복합체 패턴

문제

복합체 패턴은 앱의 핵심 모델이 트리로 표현될 수 있을 때만 사용하세요.

예를 들어 제품들상자들이라는 두 가지 유형의 객체들이 있다고 가정해 봅시다. 상자에는 여러 개의 제품들과 여러 개의 작은 상자들이 포함될 수 있습니다. 이 작은 상자들은 또한 일부 제품들 또는 더 작은 상자들등을 담을 수 있습니다.

이러한 클래스들을 사용하는 주문 시스템을 만들기로 했다고 가정해 보겠습니다. 주문들에는 포장이 없는 단순한 제품들과 제품들로 채워진 상자들 및 다른 상자들이 포함될 수 있습니다. 그러면 그러한 주문의 총가격을 어떻게 계산하시겠습니까?

복잡한 주문의 구조

하나의 주문은 여러 제품으로 구성될 수 있는데, 이 제품들은 상자에 포장될 수 있으며, 다시 그 상자들이 더 큰 상자에 포장되는 식으로 이어질 수 있습니다. 그러면 그 전체 구조는 거꾸로 된 나무와 같은 모양으로 형성될 것입니다.

당신은 이 문제에 직접적으로 접근해 볼 수 있습니다: 모든 상자를 푼 후 내부의 모든 제품을 살펴본 다음 가격의 합계를 계산하는 것입니다. 현실 세계에서는 이러한 접근 방법은 가능하나, 프로그램에서 이 작업은 덧셈 루프를 실행하는 것만큼 간단하지 않은데, 그 이유는 덧셈 루프를 실행하기 위해 진행 중인 제품들상자들의 클래스들, 상자의 중첩 수준 및 기타 복잡한 세부 사항들을 미리 알고 있어야 하기 때문입니다. 이 모든 것이 상자 및 내부 상자들을 열어 포함된 모든 제품 가격의 합계를 계산하는 직접적인 접근 방식을 어렵게 만듭니다.

해결책

복합체 패턴은 총가격을 계산하는 메서드를 선언하는 공통 인터페이스를 통해 제품들상자들 클래스들과 작업할 것을 제안합니다.

그러면 이 메서드는 어떻게 작동할까요? 제품의 경우 이 메서드는 단순히 제품 가격을 반환합니다. 상자의 경우, 이 메서드는 상자에 포함된 각 항목을 살펴보고 가격을 확인한 뒤 해당 상자의 총 가격을 반환합니다. 만약 이 항목들 중 하나가 더 작은 상자라면, 메서드는 해당 상자의 모든 내부 구성 요소의 가격이 계산될 때까지 내용물 등을 살펴봅니다. 메서드는 상자를 다룰 때 최종 가격에 포장 비용 같은 약간의 추가 비용도 추가할 수도 있습니다.

복합체 패턴이 제안하는 해결책

복합체 패턴은 객체 트리의 모든 컴포넌트들에 대해 재귀적으로 행동을 실행할 수 있도록 합니다.

이 접근 방식의 가장 큰 이점은 더 이상 트리를 구성하는 객체들의 구상 클래스들에 대해 신경 쓸 필요도, 또 물건이 단순한 제품인지 내용물이 있는 상자인지 알 필요도 없다는 점입니다. 단순히 공통 인터페이스를 통해 모두 같은 방식으로 처리하시면 됩니다. 당신이 메서드를 호출하면 객체들 자체가 요청을 트리 아래로 전달합니다.

실제상황 적용

군사 구조의 예시

군대 구조의 예시.

대부분의 국가에서 군대는 계층구조로 구성되어 있습니다. 군대는 여러 사단으로 구성되며, 사단은 여단의 집합이고, 여단은 소대의 집합이며, 소대는 또 분대로 나누어질 수 있습니다. 마지막으로 분대는 실제 군인들의 작은 집합입니다. 명령들은 계층구조의 최상위에서 내려와 모든 병사가 자신이 수행해야 할 작업을 알게 될 때까지 계층구조의 각 하위 계층으로 전달됩니다.

구조

복합 디자인 패턴의 구조복합 디자인 패턴의 구조
  1. 컴포넌트 인터페이스는 트리의 단순 요소들과 복잡한 요소들 모두에 공통적인 작업을 설명합니다.

  2. 은 트리의 기본 요소이며 하위요소가 없습니다.

    일반적으로 잎 컴포넌트들은 작업을 위임할 하위요소가 없어서 대부분의 실제 작업들을 수행합니다.

  3. 컨테이너(일명 )​는 하위 요소들​(잎 또는 기타 컨테이너)​이 있는 요소입니다. 컨테이너는 자녀들의 구상 클래스들을 알지 못하며, 컴포넌트 인터페이스를 통해서만 모든 하위 요소들과 함께 작동합니다.

    요청을 전달받으면 컨테이너는 작업을 하위 요소들에 위임하고 중간 결과들을 처리한 다음 최종 결과들을 클라이언트에 반환합니다.

  4. 클라이언트는 컴포넌트 인터페이스를 통해 모든 요소들과 작동합니다. 그 결과 클라이언트는 트리의 단순 요소들 또는 복잡한 요소들 모두에 대해 같은 방식으로 작업할 수 있습니다.

의사코드

이 예시에서는 복합체 패턴이 어떻게 그래픽 편집기에서 기하학적 모양 쌓기를 구현할 수 있도록 하는지를 살펴보겠습니다.

복합체 패턴 구조 예시

기하학적 모양 편집기 예시.

Compound­Graphic 클래스는 다른 복합 모양들을 포함한 모든 하위 모양으로 구성될 수 있는 컨테이너입니다. 복합 모양은 단순 모양과 같은 메서드들을 보유하나, 자체적으로 무언가를 수행하는 대신 복합 모양은 모든 자녀에게 재귀적으로 요청을 전달하고 이의 결과를 '요약'합니다.

클라이언트 코드는 모든 모양 클래스들에 공통인 단일 인터페이스를 통해 모든 모양과 함께 작동합니다. 따라서 클라이언트는 자신이 단순 모양과 작업하는지 복합 모양과 작업하는지를 알지 못합니다. 또 클라이언트는 매우 복잡한 객체 구조들을 형성하는 구상 클래스들에 결합하지 않고도 해당 구조들과 작업할 수 있습니다.

// 컴포넌트 인터페이스는 합성 관계의 단순 객체와 복잡한 객체 모두를 위한 공통
// 작업들을 선언합니다.
interface Graphic is
    method move(x, y)
    method draw()

// 잎 클래스는 합성 관계의 최종 객체들을 나타냅니다. 잎 객체는 하위 객체들을 가질
// 수 없습니다. 일반적으로 실제 작업을 수행하는 것은 잎 객체들이며, 복합체 객체들은
// 오로지 자신의 하위 컴포넌트에만 작업을 위임합니다.
class Dot implements Graphic is
    field x, y

    constructor Dot(x, y) { ... }

    method move(x, y) is
        this.x += x, this.y += y

    method draw() is
        // X와 Y에 점을 그립니다.

// 모든 컴포넌트 클래스들은 다른 컴포넌트들을 확장할 수 있습니다.
class Circle extends Dot is
    field radius

    constructor Circle(x, y, radius) { ... }

    method draw() is
        // X와 Y에 반지름이 R인 원을 그립니다.

// 복합체 클래스는 자식이 있을 수 있는 복잡한 컴포넌트들을 나타냅니다. 복합체
// 객체들은 일반적으로 실제 작업을 자식들에 위임한 다음 결과를 '합산'합니다.
class CompoundGraphic implements Graphic is
    field children: array of Graphic

    // 복합체 객체는 자식 리스트에 단순한 또는 복잡한 다른 컴포넌트들을 추가하거나
    // 제거할 수 있습니다.
    method add(child: Graphic) is
        // 하나의 자식을 자식들의 배열에 추가합니다.

    method remove(child: Graphic) is
        // 하나의 자식을 자식들의 배열에서 제거합니다.

    method move(x, y) is
        foreach (child in children) do
            child.move(x, y)

    // 복합체는 특정 방식으로 기본 논리를 실행합니다. 복합체는 모든 자식을
    // 재귀적으로 순회하여 결과들을 수집하고 요약합니다. 복합체의 자식들이 이러한
    // 호출들을 자신의 자식들 등으로 전달하기 때문에 결과적으로 전체 객체 트리를
    // 순회하게 됩니다.
    method draw() is
        // 1. 각 자식 컴포넌트에 대해:
        //     - 컴포넌트를 그리세요.
        //     - 경계 사각형을 업데이트하세요.
        // 2. 경계 좌표를 사용하여 점선 직사각형을 그리세요.


// 클라이언트 코드는 기초 인터페이스를 통해 모든 컴포넌트와 함께 작동합니다. 그래야
// 클라이언트 코드가 단순한 잎 컴포넌트들과 복잡한 복합체들을 지원할 수 있습니다.
class ImageEditor is
    field all: CompoundGraphic

    method load() is
        all = new CompoundGraphic()
        all.add(new Dot(1, 2))
        all.add(new Circle(5, 3, 10))
        // …

    // 선택한 컴포넌트들을 하나의 복잡한 복합체 컴포넌트로 합성합니다.
    method groupSelected(components: array of Graphic) is
        group = new CompoundGraphic()
        foreach (component in components) do
            group.add(component)
            all.remove(component)
        all.add(group)
        // 모든 컴포넌트가 그려질 것입니다.
        all.draw()

적용

복합체 패턴은 나무와 같은 객체 구조를 구현해야 할 때 사용하세요.

복합체 패턴은 공통 인터페이스를 공유하는 두 가지 기본 요소 유형들인 단순 잎들과 복합 컨테이너들을 제공합니다. 컨테이너는 잎들과 다른 컨테이너들로 구성될 수 있으며, 이를 통해 나무와 유사한 중첩된 재귀 객체 구조를 구성할 수 있습니다.

이 패턴은 클라이언트 코드가 단순 요소들과 복합 요소들을 모두 균일하게 처리하도록 하고 싶을 때 사용하세요.

복합체 패턴에 의해 정의된 모든 요소들은 공통 인터페이스를 공유하며, 이 인터페이스를 사용하면 클라이언트는 작업하는 객체들의 구상 클래스에 대해 걱정할 필요가 없습니다.

구현방법

  1. 앱의 핵심 모델이 트리 구조로 표현될 수 있는지 확인하세요. 그 후 앱을 단순 요소와 컨테이너로 분해하세요. 컨테이너는 다른 컨테이너들과 단순 요소들을 모두 포함할 수 있어야 합니다.

  2. 컴포넌트 인터페이스를 선언하세요. 이 인터페이스 내에는 복잡하고 단순한 컴포넌트 모두에 적합한 메서드들의 리스트를 포함하세요.

  3. 단순 요소들을 나타내는 잎 클래스를 생성하세요. 하나의 프로그램에는 여러 개의 서로 다른 잎 클래스들이 있을 수 있습니다.

  4. 복잡한 요소들을 나타내는 컨테이너 클래스를 만든 후, 이 클래스에 하위 요소들에 대한 참조를 저장하기 위한 배열 필드를 제공하세요. 배열은 잎과 컨테이너를 모두 저장할 수 있어야 하므로 컴포넌트 인터페이스 유형으로 선언하셔야 합니다.

    컴포넌트 인터페이스의 메서드들을 구현하는 동안 컨테이너는 대부분 작업을 하위 요소들에 위임해야 한다는 점을 기억하세요.

  5. 마지막으로 컨테이너에서 자식 요소들을 추가 및 제거하는 메서드들을 정의하세요.

    이러한 작업은 컴포넌트 인터페이스에서 선언할 수 있으나, 그리하면 잎 클래스의 메서드들이 비어 있을 것이므로 을 위반할 것입니다. 그러나 클라이언트는 트리를 구성할 때도 포함해서 모든 요소를 동등하게 처리할 수 있을 것입니다.

장단점

  • 다형성과 재귀를 당신에 유리하게 사용해 복잡한 트리 구조들과 더 편리하게 작업할 수 있습니다.
  • / . 객체 트리와 작동하는 기존 코드를 훼손하지 않고 앱에 새로운 요소 유형들을 도입할 수 있습니다.
  • 기능이 너무 다른 클래스들에는 공통 인터페이스를 제공하기 어려울 수 있으며, 어떤 경우에는 컴포넌트 인터페이스를 과도하게 일반화해야 하여 이해하기 어렵게 만들 수 있습니다.

다른 패턴과의 관계

  • 당신은 복잡한 복합체 패턴 트리를 생성할 때 빌더를 사용할 수 있습니다. 왜냐하면 빌더의 생성 단계들을 재귀적으로 작동하도록 프로그래밍할 수 있기 때문입니다.

  • 책임 연쇄 패턴은 종종 복합체 패턴과 함께 사용됩니다. 그러면 잎 컴포넌트가 요청을 받으면 해당 요청을 모든 부모 컴포넌트들의 체인을 통해 객체 트리의 뿌리​(root)​까지 전달할 수 있습니다.

  • 당신은 반복자들을 사용하여 복합체 패턴 트리들을 순회할 수 있습니다.

  • 당신은 비지터 패턴을 사용하여 복합체 패턴 트리 전체를 대상으로 작업을 수행할 수 있습니다.

  • RAM을 절약하기 위하여 복합체 패턴 트리의 공유된 잎 노드들을 플라이웨이트들로 구현할 수 있습니다.

  • 복합체 패턴 및 데코레이터는 둘 다 구조 다이어그램이 유사합니다. 왜냐하면 둘 다 재귀적인 합성에 의존하여 하나 또는 불특정 다수의 객체들을 정리하기 때문입니다.

    과 비슷하지만, 자식 컴포넌트가 하나만 있습니다. 또 다른 중요한 차이점은 는 래핑된 객체에 추가 책임들을 추가하는 반면 은 자신의 자식들의 결과를 '요약'하기만 합니다.

    그러나 패턴들은 서로 협력할 수도 있습니다: 를 사용하여 트리의 특정 객체의 행동을 확장할 수 있습니다.

  • 데코레이터복합체 패턴을 많이 사용하는 디자인들은 프로토타입을 사용하면 종종 이득을 볼 수 있습니다. 프로토타입 패턴을 적용하면 복잡한 구조들을 처음부터 다시 건축하는 대신 복제할 수 있기 때문입니다.

코드 예시

C#으로 작성된 복합체 C++로 작성된 복합체 Go로 작성된 복합체 자바로 작성된 복합체 PHP로 작성된 복합체 파이썬으로 작성된 복합체 루비로 작성된 복합체 러스트로 작성된 복합체 스위프트로 작성된 복합체 타입스크립트로 작성된 복합체