[Design Pattern] Composite Pattern

Main·2024년 8월 22일
0

Design Pattern

목록 보기
7/7

Composite Pattern ?

Composite 패턴은 객체를 트리 구조로 구성하여 개별 객체와 객체 그룹을 동일하게 다루는 데 사용되는 구조적 디자인 패턴입니다. 이 패턴은 객체들이 개별 객체와 객체의 조합(Composite)을 일관되게 다룰 수 있게 해줍니다. 주로 트리 구조를 표현할 때 유용하며, 예를 들어 파일 시스템, 그래픽 UI 요소, 조직 구조 등을 모델링할 때 많이 사용됩니다.

composite_pattern.png


Composite Pattern의 구성요소

Component : 트리 구조의 모든 객체가 상속받는 공통 인터페이스입니다. 여기에는 모든 객체가 가져야 할 공통 메서드들이 정의됩니다.

Leaf : 실제 작업을 수행하는 객체입니다. 트리 구조의 말단 노드에 해당하며, 더 이상 하위 객체를 가지지 않습니다.

Composite : 다른 컴포넌트(Leaf나 다른 Composite 객체)를 자식으로 가지는 복합 객체입니다. 자식 객체에 대한 작업을 수행하거나 자식 객체에 작업을 위임합니다.

복합체 패턴의 핵심은 재귀적 구조하위 객체에 대한 작업 위임입니다. 복합 객체는 하위 객체들을 순회하며 작업을 수행하고, 이러한 동작은 재귀적으로 이루어지기 때문에 복합 객체와 단일 객체를 동일한 방식으로 다룰 수 있습니다.


Composite Pattern의 장점

  • 일관된 인터페이스 제공 : Composite 패턴을 사용하면 단일 객체(Leaf)와 복합 객체(Composite)를 동일한 인터페이스로 처리할 수 있습니다. 이로 인해 클라이언트 코드에서 객체 유형에 따라 조건문으로 분기하는 필요성이 줄어들어 코드가 단순해집니다.
  • 재귀적 구조 지원 : Composite 패턴은 재귀적으로 트리 구조를 구성하고 관리할 수 있습니다. 이는 복잡한 계층 구조를 처리할 때 유용하며, 하위 객체에 대한 작업을 재귀적으로 적용할 수 있습니다.
  • 확장성 : 새로운 종류의 구성 요소(예: 새로운 Leaf 또는 Composite 객체)를 추가할 때 기존 코드를 거의 수정하지 않아도 됩니다. 이는 OCP(개방-폐쇄 원칙, Open-Closed Principle)에 부합하여, 시스템의 유지보수성과 확장성을 높여줍니다.
  • 관리 용이성 : 복합 객체와 단일 객체를 동일한 방식으로 다루므로, 복잡한 구조를 관리하기 쉽게 만듭니다. 예를 들어, 폴더와 파일을 동일한 방식으로 처리할 수 있어 파일 시스템과 같은 계층 구조를 효과적으로 관리할 수 있습니다.

Composite Pattern의 단점

  • 복잡성 증가 : Composite 패턴을 구현할 때 구조가 지나치게 복잡해질 수 있습니다. 특히, 트리 구조가 깊어지면 객체 관리 및 메서드 호출이 복잡해질 수 있습니다. 이로 인해 코드의 이해도와 유지보수성이 떨어질 수 있습니다.
  • 객체 관리 어려움 : 복합 객체가 많은 하위 객체를 포함할 경우, 하위 객체들을 관리하는 것이 어려울 수 있습니다. 하위 객체의 추가, 삭제, 검색 등의 작업이 복잡해질 수 있으며, 이로 인해 성능 저하가 발생할 수 있습니다.
  • 단일 객체와 복합 객체 간 차이 모호 : 단일 객체와 복합 객체를 동일하게 처리할 수 있다는 장점이 있지만, 때로는 이 둘의 차이가 모호해질 수 있습니다. 특정 상황에서는 복합 객체와 단일 객체를 명확히 구분해야 할 필요가 있는데, Composite 패턴을 사용하면 이 구분이 어려워질 수 있습니다.

Composite Pattern 예시 코드

export class Component {
  operation() {}
}
import { Component } from "./Component";

export class Leaf extends Component {
  operation(): void {
    console.log(JSON.stringify(this) + " Leaf 호출");
  }
}
import { Component } from "./Component";

export class Composite extends Component {
  // Leaf 와 Composite 객체 모두를 저장하여 관리하는 내부 리스트
  components: Array<Component> = [];

  public add(c: Component) {
    this.components.push(c); // 리스트 추가
  }

  public remove(c: Component) {
    this.components = this.components.filter((component) => component !== c); // 리스트 삭제
  }

  public operation() {
    console.log(JSON.stringify(this) + " 호출");

    // 내부 리스트를 순회하여, 단일 Leaf이면 값을 출력하고,
    // 또다른 서브 복합 객체이면, 다시 그 내부를 순회하는 재귀 함수 동작이 된다.
    for (let i = 0; i < this.components.length; i++) {
      this.components[i].operation(); // 자기 자신을 호출(재귀)
    }
  }

  public getChild() {
    return this.components;
  }
}
import { Composite } from "./lib/Composite";
import { Leaf } from "./lib/Leaf";

// 1. 최상위 복합체 생성
const composite1 = new Composite();

// 2. 최상위 복합체에 저장할 Leaf와 또다른 서브 복합체 생성
const leaf1 = new Leaf();
const composite2 = new Composite();

// 3. 최상위 복합체에 개체들을 등록
composite1.add(leaf1);
composite1.add(composite2);

// 4. 서브 복합체에 저장할 Leaf 생성
const leaf2 = new Leaf();
const leaf3 = new Leaf();
const leaf4 = new Leaf();

// 5. 서브 복합체에 개체들을 등록
composite2.add(leaf2);
composite2.add(leaf3);
composite2.add(leaf4);

// 6. 최상위 복합체의 모든 자식 노드들을 출력
composite1.operation();

operation 메서드를 호출하게 되면, 단일체일 경우 값이 호출 되고, 복합체일 경우 자기 자신을 호출하는 재귀 함수에 의해 저장하고 있는 하위 Leaf 객체들을 순회하여 호출하게 됩니다.


Composite Pattern Flow

Composite_Pattern_Flow.png

Composite_Pattern_Flow_console.png


Composite Pattern 예제

File SystemComposite Pattern으로 구현하는 예제입니다.

// Component: 파일과 폴더의 공통 메서드 정의
export class FileSystemComponent {
  getName(): string {
    return "";
  }
  getSize(): number {
    return 0;
  }
  print(indent: string) {}
}
// Leaf: 파일 객체
export class File extends FileSystemComponent {
    constructor(private name: string, private size: number) {
      super();
    }

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

    getSize(): number {
        return this.size;
    }
    
    // 파일 정보를 출력
    print(indent: string = ''): void {
        console.log(`${indent}↳ File: ${this.getName()} (${this.getSize()} bytes)`);
    }
}
// Composite: 폴더 객체
export class Folder extends FileSystemComponent {
  // 폴더 내부에 담긴 파일 또는 폴더 리스트
  private components: FileSystemComponent[] = [];

  constructor(private name: string) {
    super();
  }

  add(c: FileSystemComponent): void {
    this.components.push(c);
  }

  remove(c: FileSystemComponent): void {
    this.components = this.components.filter((component) => component !== c);
  }

  getName(): string {
    return this.name;
  }
  
  // 폴더 내부의 모든 파일과 폴더의 크기를 합산하여 반환
  getSize(): number {
    return this.components.reduce((total, comp) => total + comp.getSize(), 0);
  }

  // 폴더의 정보를 출력하고, 그 내부에 담긴 파일과 폴더를 재귀적으로 출력
  print(indent: string = ""): void {
    console.log(
      `${indent}↳ Folder: ${this.getName()} (${this.getSize()} bytes)`,
    );
    for (const component of this.components) {
      component.print(indent + "  ");
    }
  }
}
const mainFolder = new Folder('MainFolder');

const subFolder1 = new Folder('SubFolder1');
const subFolder2 = new Folder('SubFolder2');

const file1 = new File('File1.txt', 500);
const file2 = new File('File2.txt', 300);
const file3 = new File('File3.txt', 200);
const file4 = new File('File4.txt', 100);

subFolder1.add(file2);
subFolder1.add(file3);

subFolder2.add(file4);

mainFolder.add(subFolder1);
mainFolder.add(subFolder2);
mainFolder.add(file1);

mainFolder.print();

file_system_composite_pattern.png

file_system_composite_pattern_console.png

  • File 클래스는 Leaf에 해당합니다. 각 파일은 크기(size)와 이름(name)을 가지고 있습니다.
  • Folder 클래스는 Composite에 해당합니다. 이 상자는 다른 파일이나 다른 폴더를 담을 수 있습니다. 즉, 폴더 안에 폴더가 있을 수 있고, 그 안에 또 다른 파일이나 폴더가 있을 수 있습니다.

위 코드 예제는 Composite 패턴을 활용하여 파일 시스템을 모델링하는 방법을 보여줍니다. 이 패턴을 통해 파일과 폴더를 동일한 방식으로 처리할 수 있으며, 폴더 내의 복잡한 계층 구조도 쉽게 관리할 수 있습니다.


정리

Composite 패턴은 객체를 트리 구조로 구성하여 개별 객체(Leaf)와 복합 객체(Composite)를 동일하게 처리할 수 있도록 하는 강력한 디자인 패턴입니다. 이 패턴을 사용하면 복잡한 계층 구조를 단순화하고, 개별 객체와 객체 그룹을 동일한 인터페이스로 다룰 수 있어 코드의 유연성과 확장성을 크게 향상시킬 수 있습니다. 그러나 Composite 패턴은 구조가 복잡해질 수 있으며, 특히 트리 구조가 깊어질수록 객체 관리와 메서드 호출이 복잡해질 수 있습니다. 또한, 단일 객체와 복합 객체 간의 차이가 모호해질 수 있어 특정 상황에서는 명확한 구분이 어려울 수 있습니다.

참고 사이트

https://inpa.tistory.com/entry/GOF-💠-복합체Composite-패턴-완벽-마스터하기

profile
함께 개선하는 프론트엔드 개발자

0개의 댓글