Composite

chrishan·2021년 6월 26일
0

Design pattern

목록 보기
7/12

Intent

Composite는 객체를 트리 구조로 구성한 다음 개별 객체인것처럼 이러한 구조로 작업 할 수 있는 구조적 Design pattern입니다.

Problem

Composite pattern을 사용하는 것은 앱의 핵심 모델을 트리로 표현 할 수 있는 경우에만 의미가 있습니다.
예를 들어 ProductsBoxes라는 두 가지 유형의 개체가 있다고 가정합니다. 상자에는 여거 개의 Products과 여러 개의 작은 Boxes가 포함될 수 있습니다. 이 작은 Boxes에는 일부 Products 또는 더 작은 Boxes 등도 담을 수 있습니다.
이러한 클래스를 사용하는 주문 시스템을 만들기로 결정했다고 가정해 보겠습니다. 주문에는 포장이 없는 단순한 Product와 Product로 재워진 Box 및 기타 Box가 포함될 수 있습니다. 그러한 주문의 총 가격을 어떻게 결정 하시겠습니까?

직접 접근 방식을 시도해 볼 수 있습니다. 모든 상자를 풀고 모든 제품을 살표본 다음 합계를 계산하십시오. 현실 세계에서는 가능 하겠지만 프로그램에서는 루프를 실행하는 것만큼 간단하지 않습니다. 진행중인 ProductsBoxes의 클래스, 상자의 중첩 수준 및 기타 불쾌한 세부 정보를 미리 알아야 합니다. 이 모든 것이 직접적인 접근 방식을 너무 어색하거나 불가능하게 만듭니다.

Solution

Composite pattern은 총 가격을 계산하는 방법을 선언하는 공통 Interface를 통해 ProductsBoxex로 작업 할 것을 제안합니다.
How would this method work? Products의 경우 단순히 Products 가격을 반환합니다. Boxes의 경우 Boxes에 포함 된 각 항목을 살펴보고 가격을 물어 본 다음 이 Boxes의 합계를 반환합니다. 이러한 항목 중 하나가 더 작은 Box인 경우 모든 내부 구성 요소의 가격이 계산 될 때까지 해당 Box도 내용물을 넘기기 시작합니다. Box는 포장 비용과 같은 최종 가격에 추가 비용을 추가 할 수도 있습니다.

이 접근 방식의 가장 큰 이점은 Tree를 구성하는 객체의 구체적인 클래스에 대해 신경 쓸 필요가 없다는 것입니다. 물건이 단순한 제품인지 정교한 상자인지 알 필요가 없습니다. 공통 Interface를 통해 동일하게 처리 할 수 있습니다. 메서드를 호출하면 개체 자체가 Tree 아래로 요청을 전달합니다.

Real-World Analogy


대부분 국가의 군대는 계층 구조로 구성됩니다. 군대는 여러 부서로 구성됩니다. 사단은 여단의 집합이고 여단은 소대로 구성되며 분대로 나눌 수 있습니다. 마지막으로 분대는 실제 군인의 작은 그룹입니다. 명령은 계층 구조의 맨 위에 주어지며 모든 병사가 수행해야 할 작업을 알 때까지 각 레벨로 전달됩니다.

Structure


1. Component Interface는 Tree의 단순 요소와 복잡한 요소 모두에 공통적인 작업을 설명합니다.
2. Leaf는 하위 요소가 없는 나무의 기본 요소입니다.
일반적으로 Leaf 구성 요소는 작업을 위임 할 사람이 없기 때문에 대부분의 실제 작업을 수행합니다.
3. Container(aka composite)는 sub-elements가 있는 element입니다. Container는 작식의 구체적인 클래스를 알지 못합니다. Component Interface를 통해서만 모든 하위 요소와 함께 작동합니다.
요청을 받으면 Container는 작업을 하위 요소에 위임하고 중간 결과를 처리 한 다음 최종 결과를 Client에 반환합니다.
4. Client는 Component Interface를 통해 모든 요소와 함께 작동합니다. 결과적으로 Client는 Tree의 단순하거나 복잡한 요소 모두에서 동일한 방식으로 작업 할 수 있습니다.

Pseudocode

이 예제에서 Composite pattern을 사용하면 그래픽 편집기에서 기하학적 모양 스택을 구현할 수 있습니다.

CompoundGraphic 클래스는 다른 복합 모양을 포함하여 여러 하위 모양을 구성 할 수 있는 Container입니다. 복합 모양은 단순 모양과 동일한 방법을 사용합니다. 그러나 자체적으로 무언가를 하는 대신 복합 형태는 요청을 모든 자식에게 재귀적으로 전달하고 결과를 "합산"합니다.
클라이언트 코드는 모든 Shapes 클래스에 공통된 단일 Interface를 통해 모든 Shapes에서 작동합니다. 따라서 클라이언트는 간단한 모양으로 작동하는지 복합 모양으로 작동하는지 알지 못합니다. 클라이언트는 해당 구조를 형성하는 구체적인 클래스에 연결되지 않고도 매우 복잡한 개체 구조로 작업 할 수 있습니다.

// The component interface declares common operations for both
// simple and complex objects of a composition.
interface Graphic is
    method move(x, y)
    method draw()

// The leaf class represents end objects of a composition. A
// leaf object can't have any sub-objects. Usually, it's leaf
// objects that do the actual work, while composite objects only
// delegate to their sub-components.
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
        // Draw a dot at X and Y.

// All component classes can extend other components.
class Circle extends Dot is
    field radius

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

    method draw() is
        // Draw a circle at X and Y with radius R.

// The composite class represents complex components that may
// have children. Composite objects usually delegate the actual
// work to their children and then "sum up" the result.
class CompoundGraphic implements Graphic is
    field children: array of Graphic

    // A composite object can add or remove other components
    // (both simple or complex) to or from its child list.
    method add(child: Graphic) is
        // Add a child to the array of children.

    method remove(child: Graphic) is
        // Remove a child from the array of children.

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

    // A composite executes its primary logic in a particular
    // way. It traverses recursively through all its children,
    // collecting and summing up their results. Since the
    // composite's children pass these calls to their own
    // children and so forth, the whole object tree is traversed
    // as a result.
    method draw() is
        // 1. For each child component:
        //     - Draw the component.
        //     - Update the bounding rectangle.
        // 2. Draw a dashed rectangle using the bounding
        // coordinates.


// The client code works with all the components via their base
// interface. This way the client code can support simple leaf
// components as well as complex composites.
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))
        // ...

    // Combine selected components into one complex composite
    // component.
    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 components will be drawn.
        all.draw()

Applicability

Tree와 같은 개체 구조를 구현해야 할 때 Composite pattern을 사용합니다.

Composite pattern은 공통 Interface를 공유하는 두가지 기본 요소 유형을 제공합니다. Container는 leaves와 complex containers로 구성 될 수 있습니다. 이를 통해 Tree와 유사한 중첩 된 재귀 객체 구조를 구성 할 수 있습니다.

클라이언트 코드가 단순 요소와 복잡한 요소를 모두 균일하게 처리하도록 하려면 Composite pattern을 사용하십시오.

Composite pattern으로 정의 된 모든 요소는 공통 Interface를 공유합니다. 이 Interface를 사용하면 클라이언트는 작업하는 객체의 구체적인 클래스에 대해 걱정할 필요가 없습니다.

How to Implement

  1. 앱의 핵심 모델이 트리 구조로 표현 될 수 있는지 확인하십시오. 간단한 요소와 컨테이너로 나누십시오. 컨테이너는 단순 요소와 기타 컨테이너를 모두 포함 할 수 있어야합니다.

  2. 단순 구성 요소와 복잡한 구성 요소 모두에 적합한 메서드 목록으로 구성 요소 인터페이스를 선언합니다.

  3. 간단한 요소를 나타내는 리프 클래스를 만듭니다. 프로그램에는 여러 리프 클래스가있을 수 있습니다.

  4. 복잡한 요소를 나타내는 컨테이너 클래스를 만듭니다. 이 클래스에서 하위 요소에 대한 참조를 저장하기위한 배열 필드를 제공합니다. 배열은 잎과 컨테이너를 모두 저장할 수 있어야하므로 구성 요소 인터페이스 유형으로 선언해야합니다.

    컴포넌트 인터페이스의 메소드를 구현하는 동안 컨테이너는 대부분의 작업을 하위 요소에 위임해야합니다.

  5. 마지막으로 컨테이너에서 자식 요소를 추가하고 제거하는 방법을 정의합니다.

    이러한 작업은 구성 요소 인터페이스에서 선언 할 수 있습니다. 이 메서드는 리프 클래스에서 비어 있기 때문에 인터페이스 분리 원칙을 위반합니다. 그러나 클라이언트는 트리를 구성 할 때도 모든 요소를 동일하게 처리 할 수 있습니다.

Pros and Cons

O 복잡한 트리 구조로 더 편리하게 작업 할 수 있습니다. 다형성과 재귀를 유리하게 사용하십시오.
O 개방 / 폐쇄 원칙. 이제 개체 트리와 함께 작동하는 기존 코드를 손상시키지 않고 앱에 새 요소 유형을 도입 할 수 있습니다.
X 기능이 너무 많이 다른 클래스에 대한 공통 인터페이스를 제공하는 것은 어려울 수 있습니다. 특정 시나리오에서는 구성 요소 인터페이스를 지나치게 일반화하여 이해하기 어렵게 만들어야합니다.

Relations with Other Patterns

  • 재귀 적으로 작동하도록 구성 단계를 프로그래밍 할 수 있으므로 복잡한 Composite 트리를 만들 때 Builder를 사용할 수 있습니다.

  • Chain of Responsibility은 종종 Composite와 함께 사용됩니다. 이 경우 leaf component 요소가 요청을 받으면 모든 상위 구성 요소의 체인을 통해 객체 트리의 루트까지 전달할 수 있습니다.

  • Iterators를 사용하여 Composite 트리를 탐색 할 수 있습니다.

  • Visitor를 사용하여 전체 Composite 트리에서 작업을 실행할 수 있습니다.

  • Composite 트리의 공유 리프 노드를 Flyweights로 구현하여 RAM을 절약 할 수 있습니다.

  • CompositeDecorator는 둘 다 재귀 적 구성에 의존하여 개방형 개체 수를 구성하기 때문에 유사한 구조 다이어그램을 가지고 있습니다.

    DecoratorComposite와 비슷하지만 하나의 하위 구성 요소 만 있습니다. 또 다른 중요한 차이점이 있습니다. Decorator는 래핑 된 개체에 추가 책임을 추가하는 반면 Composite는 자식의 결과를 "합계"합니다.

    그러나 패턴은 상호 작용할 수도 있습니다. 데코레이터를 사용하여 합성 트리에서 특정 개체의 동작을 확장 할 수 있습니다.

  • CompositeDecorator를 많이 사용하는 디자인은 종종 Prototype을 사용하여 이점을 얻을 수 있습니다. 패턴을 적용하면 복잡한 구조를 처음부터 재구성하는 대신 복제 할 수 있습니다.

0개의 댓글