[Structural Patterns] - Composite

Lee Jeong Min·2022년 1월 9일
0

디자인 패턴

목록 보기
9/23
post-thumbnail

의도

복합은 객체를 트리 구조로 합성해 하나의 물체인 것처럼 작업할 수 있는 구조설계 패턴이다.

복합 패턴은 트리 구조를 구축해야 하는 대부분의 문제에 대해 인기 있는 솔루션이 되었다. 복합 패턴의 가장 큰 특징은 전체 트리 구조에서 메서드를 재귀적으로 실행하고 결과를 합산하는 기능이다.

문제

복합 패턴을 사용하는 것은 앱의 핵심 모델을 트리로 나타낼 수 있는 경우에만 의미가 있다.

예를 들어, 두 가지 객체 유형(제품 및 상자)이 있다고 가정하자. 상자 하나에 여러 개의 제품 및 여러 개의 작은 상자가 포함될 수 있다. 이러한 작은 상자에는 일부 제품 또는 더 작은 상자 등이 포함될 수도 있다.

이러한 클래스를 사용하는 주문 시스템을 만들기로 결정했다고 가정해보자. 주문에는 포장 없이 간단한 제품뿐만 아니라 제품이 가득 들어있는 상자도 포함될 수 있다. 이러한 상황에서 이 제품들의 총 가격을 어떻게 계산할 것인가?

주문은 상자에 포장된 다양한 상품, 더 큰 상자에 포장된 상품등으로 구성될 수 있다. 전체 구조물은 거꾸로 된 나무처럼 보인다.

포장 상자를 모두 열고 모든 제품을 검토한 다음 총계를 계산하는 직접적인 방법도 있지만 프로그램에서 loop를 돌리는 것만큼 쉬운일은 아니다. 이를 계산하려면, 상품과 상자의 종류와 같이 다른 세부사항들을 미리 알고 있어야 한다. 이러한 모든 것이 직접적인 접근을 너무 어색하거나 심지어 불가능하게 만든다.

해결책

복합 패턴은 총 가격 계산 방법을 선언하는 공통 인터페이스를 통해 제품 및 상자로 작업할 것을 제안한다.

제품의 경우 단순히 제품의 가격을 리턴시키고 박스의 경우 박스안에 들어있는 각 품목을 살펴본 후 가격의 총계를 리턴시키는 방법으로 동작한다.

복합 패턴을 사용하면 객체 트리의 모든 구성 요소에 대해 동작을 반복적으로 실행할 수 있다.

이 접근 방식의 가장 큰 이점은 트리를 구성하는 객체의 구체적인 종류에 신경쓸 필요가 없다는 것이다.(공통 인터페이스를 통해 모두 동일하게 취급) 메서드를 호출하면 객체 자체가 트리 아래로 요청을 전달한다.

현실 유사성

군대 구조의 한 예이다.

대부분 국가의 군대는 계층 구조로 되어 있다. 군대는 여러개의 사단으로 구성되어 있고, 한 개의 사단은 여단이고, 한개의 여단은 소대로 구성되어 분대로 나눌 수 있다. 마지막으로 분대는 실제 군인들의 작은 그룹이다. 명령은 위계질서의 맨 위에서 내려지고, 모든 병사들이 무엇을 해야하는지 알 때 까지 각 단계로 전해진다.

구조

  1. Component 인터페이스는 트리의 단순 요소와 복잡한 요소 모두에 공통적인 작업을 설명한다.

  2. leaf는 하위 요소가 없는 트리의 기본 요소이다.
    보통 leaf 구성 요소는 작업을 위임할 사람이 없어 대부분의 실제 작업을 한다.

  3. Composite는 leaf 또는 기타 container와 같은 하위 요소를 가진 요소이다. 컨테이너는 자식들의 구체적인 계급을 알지 못한다. 구성 요소 인터페이스를 통해서만 모든 하위 요소와 함께 작동한다.
    요청을 받으면 컨테이너는 작업을 하위 요소에 위임하고 중간 결과를 처리한 다음 클라이언트에게 최종 결과를 반환한다.

  4. 클라이언트는 구성요소 인터페이스를 통해 모든 요소와 함께 작동한다. 결과적으로 클라이언트는 트리의 단순하거나 복잡한 요소 모두에 대해 동일한 방식으로 작업할 수 있다.

적용가능성

  • 트리와 같은 객체 구조를 구현해야하는 경우 합성 패턴을 사용한다.

  • 클라이언트 코드가 단순 요소와 복잡한 요소를 모두 균일하게 처리하도록 하려면 이 패턴을 사용해라

장단점

장점

  • 복잡한 트리 구조를 보다 편리하게 사용할 수 있다. 다형성 및 재귀 기능을 사용할 수 있다.

  • 열기/닫기 원리. 객체 트리와 함께 작동하는 기존 코드를 중단하지 않고 새로운 요소 유형을 앱에 도입할 수 있다.

단점

  • 기능이 너무 다른 클래스에 공통 인터페이스를 제공하는 것은 어려울 수 있다. 특정 시나리오에서는 구성 요소 인터페이스를 지나치게 일반화해야하므로 이해하기 어렵다.

Composite in TypeScript

TypeScript의 패턴 사용

복잡도: ★★☆

인기: ★★☆

사용 예: 복합 패턴은 TypeScript 코드에서 매우 일반적이다. 사용자 인터페이스 구성 요소 또는 그래프와 함께 작동하는 코드의 계층을 나타내는 데 자주 사용된다.

식별: 객체 트리가 있고 트리의 각 개체가 동일한 클래스 계층의 일부인 경우 복합 객체일 가능성이 높다. 이러한 클래스의 메서드가 트리의 하위 객체에 작업을 위임하고 계층의 기본 클래스/인터페이스를 통해 작업하는 경우, 이는 복합 작업일 확률이 높다.

index.ts

// 기본 컴포넌트 클래스는 복합 및 단순 객체의 공통 수행작업을 선언한다.
abstract class Component {
  protected parent: Component;

  // 트리 구조에서 부모 컴포넌트에 접근할 수 있게 셋팅하는 인터페이스를 선언할 수 있다.
  public setParent(parent: Component) {
    this.parent = parent;
  }
  public getParent(): Component {
    return this.parent;
  }

  // 또한 child를 관리할 수 있는 수행작업 권한을 정의하는 것이 이득일 수도 있다.
  public add(component: Component): void {}

  public remove(component: Component): void {}

  public isComposite(): boolean {
    return false;
  }

  // 기본 구성 요소는 일부 기본동작을 구현하거나 동작을 포함하는 메서드를 '추상'으로 선언하여
  // 특정 클래스에 남겨둘 수 있다.
  public abstract operation(): string;
}

// Leaf 클래스는 복합 객체의 마지막을 의미한다.
class Leaf extends Component {
  public operation(): string {
    return 'Leaf';
  }
}

class Composite extends Component {
  protected children: Component[] = [];

  public add(component: Component): void {
    this.children.push(component);
    component.setParent(this);
  }

  public remove(component: Component): void {
    const componentIndex = this.children.indexOf(component);
    this.children.splice(componentIndex, 1);

    component.setParent(null);
  }

  public isComposite(): boolean {
    return true;
  }

  public operation(): string {
    const results = [];
    for (const child of this.children) {
      results.push(child.operation());
    }

    return `Branch(${results.join('+')})`;
  }
}

function clientCode(component: Component) {
  // ...

  console.log(`RESULT: ${component.operation()}`);

  // ...
}

const simple = new Leaf();
console.log("Client: I've got a simple component:");
clientCode(simple);
console.log('');

const tree = new Composite();
const branch1 = new Composite();
branch1.add(new Leaf());
branch1.add(new Leaf());
const branch2 = new Composite();
branch2.add(new Leaf());
tree.add(branch1);
tree.add(branch2);
console.log("Client: Now I've got a composite tree:");
clientCode(tree);
console.log('');

function clientCode2(component1: Component, component2: Component) {
  // ...

  if (component1.isComposite()) {
    component1.add(component2);
  }
  console.log(`RESULT: ${component1.operation()}`);

  // ...
}

console.log("Client: I don't need to check the components classes even when managing the tree:");
clientCode2(tree, simple);

결과

요약

복합은 객체를 트리구조로 합성해 하나의 물체인 것처럼 작업할 수 있는 구조 설계 패턴
→ 가장 큰 특징: 메서드를 재귀적으로 실행하고 결과를 합산하는 기능

공통 인터페이스를 통해 작업에 대한 메서드를 정의하여 사용한다.

참고 사이트

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글