Composite[Design Pattern]

SnowCat·2023년 3월 7일
0

Design Pattern

목록 보기
9/23
post-thumbnail

의도

  • 복합체 패턴 => 객체들을 트리 구조들로 구성하고, 이러한 구조들과 개별 객체들처럼 작업할 수 있도록 하는 구조 패턴

문제

앱의 핵심 모델이 트리로 표현 가능할때만 사용할것

  • 제품, 상자의 2개의 객체들이 있다 가정해보자. 상자 내부에는 여러개의 다른 상자들과 여러 제품들이 포함될 수 있다.
  • 주문 시 상자안의 가격을 계산하고자 하면 트리를 순회하면서 합을 계산할 수 있을 것이다. 하지만 이를 위해서는 상자에 무엇이 들어있나를 알고 있어야 하는 어려움이 있다.

해결책

  • 총가격을 계산하는 메서드를 선언하는 공통 인터페이스를 통해 작업
  • 객체의 경우 값을 반환하고 상자가 나올 경우 하위 요소들의 가격의 합을 반환하도록 하는 재귀함수를 작성
  • 재귀적 접근방식을 통해 트리를 구성하는 객체들의 구상 클래스인지, 상자의 중첩 상태가 어떻게 되는지를 더이상 신경쓸 필요가 사라지게 됨

구조

// 여러 요소들 전부에 적용될 수 있는 공통 요소들을 정의하는 추상 클래스
abstract class Component {
    protected parent!: Component | null;

    public setParent(parent: Component | null) {
        this.parent = parent;
    }

    public getParent(): Component | null {
        return this.parent;
    }

    public add(component: Component): void { }

    public remove(component: Component): void { }

    public isComposite(): boolean {
        return false;
    }

    public abstract operation(): string;
}

// 트리에 사용될 기본 요소로 하위요소 없이 실제 작업을 수행함
class Leaf extends Component {
    public operation(): string {
        return 'Leaf';
    }
}

/* composite에서 실제 객체 정의
 * Leaf 또는 다른 Component를 자식으로 가짐
 * 부모 클래스는 자식 클래스가 무엇인지를 알지 못하고, 컴포넌트 인터페이스를 통해서만 하위 요소들과 작동
 */

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); // RESULT: Leaf
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); //RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
console.log('');

// child 관리가 추상 클래스에서 이미 정의되어있기 때문에 구상 클래스 구현과 관계없이 인터페이스를 구현한 컴포넌트끼리의 결합이 가능해짐
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); //RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)

적용

  • 트리 객체 구조를 구현할 때 사용
    컨테이너를 잎과 다른 컨테이너로 나누고, 나무와 유사한 중첩된 재귀 구조 구성
  • 클라이언트 코드가 단순 요소와 복합 요소를 균일하게 처리하고자 할 때 사용
    복합체 패턴에 의해 정의된 무든 요소들은 공통 인터페이스를 공유하기 때문에 구상 클래스에 대해 신경을 쓰지 않아도 됨

구현방법

  1. 앱의 핵심 모델이 트리 구조로 표현된다면 앱을 단순 요소와 컨테이너로 분해
  2. 컴포넌트 인터페이스를 선언하고, 모든 컴포넌트가 공유하는 적합한 메서드를 작성
  3. 단순 요소들을 나타내는 잎 클래스를 생성
  4. 복잡한 요소들을 나타낸느 컨테이너 클래스를 만들고, 하위 요소들을 저장하기 위한 배열 필드 제공
    잎과 컴포넌트 모두를 저장할 수 있어야 하기 떄문에 타입은 컴포넌트 인터페이스 유형이 되야 함
    컨테이너는 대부분의 작업을 하위 요소들에 위임하도록 인터페이스 메서드 구현
  5. 자식들을 추가하고 제거하는 메서드를 컨테이너에 정의

장단점

  • 다형성과 재귀를 사용해 복잡한 트리 구조의 객체를 편하게 작업할 수 있음
  • 객체 트리와 작동하는 코드를 훼손하지 않고 기능 추가를 할 수 있게 해 개방, 폐쇄 원칙 준수
  • 기능이 너무 다른 클래스들에는 공통 인터페이스르 제공하기 어려움

출처:
https://refactoring.guru/ko/design-patterns/composite

profile
냐아아아아아아아아앙

0개의 댓글