Dev/디자인패턴

[타입스크립트로 살펴보는 디자인패턴 11] 컴포지트 패턴

싯벨트 2025. 1. 3. 18:19
728x90

🙋‍♂️ 디자인패턴 구현코드 깃헙

컴포지트 패턴(Composite Pattern)

컴포지트 패턴으로 객체 트리구조로 구성해서 부분-전체 계층 구조를 구현한다. 컴포지트 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있다.

각 항목들을 트리구조로 구성하여 부분-전체 계층 구조(part-whole hierarchy)를 생성하면 개별 객체와 복합 객체에서 동일한 방식을 적용할 수 있다. 자식 원소가 있는 원소를 노드(node)라 부르고, 자식이 없는 원소를 잎(leaf)이라고 부른다.

컴포지트 패턴 클래스 다이어그램

  • 클라이언트는 Component 인터페이스를 사용해서 복합 객체 내의 객체들을 조작할 수 있다.
  • Component는 복합 객체 내에 들어있는 모든 객체의 인터페이스를 정의한다. 복합 노드와 잎에 관한 메서드까지 정의한다.
  • Compsite는 자식이 있는 구성 요소의 행동을 정의하고, 자식 구성 요소를 저장하는 역할을 맡는다.

코드

menu.component.ts

컴포넌트 추상 클래스는 복합 객체와 개별 객체 모두의 슈퍼 클래스가 될 것이므로, 기본적으로 모든 메서드를 구현했다.

import { UnsupportedOperationError } from "./composite.error";

export abstract class MenuComponent {
    add(menuCompoment: MenuComponent): void {
        throw new UnsupportedOperationError();
    }
    remove(menuCompoment: MenuComponent): void {
        throw new UnsupportedOperationError();
    }
    getChild(i: number): MenuComponent {
        throw new UnsupportedOperationError();
    }

    getName(): string {
        throw new UnsupportedOperationError();
    }
    getDescription(): string {
        throw new UnsupportedOperationError();
    }
    getPrice(): number {
        throw new UnsupportedOperationError();
    }
    isVegetarian(): boolean {
        throw new UnsupportedOperationError();
    }

    print(): void {
        throw new UnsupportedOperationError();
    }
}

waitress.ts

allMenus의 print() 메서드를 호출한다.

import { MenuComponent } from "./menu.component";

export class Waitress {
    constructor(private allMenus: MenuComponent) {}

    printMenu(): void {
        this.allMenus.print();
    }
}

menu.ts

add 메서드를 통해 추가된 MenuComponet 를 순회하며 메뉴를 출력한다.

import { MenuComponent } from "./menu.component";

export class Menu extends MenuComponent {
    private menuComponents: MenuComponent[] = [];

    constructor(
        private name: string,
        private description: string,
    ) {
        super();
    }
    
    add(menuCompoment: MenuComponent): void {
        this.menuComponents.push(menuCompoment);
    }
    remove(menuCompoment: MenuComponent): void {
        this.menuComponents.filter((component) => component !== menuCompoment);
    }
    getChild(i: number): MenuComponent {
        return this.menuComponents[i];
    }

    getName(): string {
        return this.name;
    }

    getDescription(): string {
        return this.description;
    }

    print(): void {
        console.log(`\\n${this.getName()}, ${this.getDescription()}`);
        console.log("======================");

        for (const menuComponent of this.menuComponents) {
            menuComponent.print();
        }
    }
}

컴포지트 패턴을 살펴보면 한 클래스가 메뉴 관련 작업 처리와 계층 구조를 관리하는 2가지 역할을 처리하면서 “단일 책임 원칙”을 위배하고 있다. 그치만 동시에 클라이언트가 복합 객체와 단일 객체를 구분할 수 있는 투명성(transparency)을 확보했다. 이처럼 디자인 원칙은 무조건 따르는 것이 아니라 상황에 따라 원칙을 깨는 경우도 있다는 것을 고려하자.

참고문서

헤드퍼스트 디자인패턴, 한빛미디어