Composite 패턴은 객체를 트리 구조로 구성하여 개별 객체와 객체 그룹을 동일하게 다루는 데 사용되는 구조적 디자인 패턴입니다. 이 패턴은 객체들이 개별 객체와 객체의 조합(Composite)을 일관되게 다룰 수 있게 해줍니다. 주로 트리 구조를 표현할 때 유용하며, 예를 들어 파일 시스템, 그래픽 UI 요소, 조직 구조 등을 모델링할 때 많이 사용됩니다.
Component : 트리 구조의 모든 객체가 상속받는 공통 인터페이스입니다. 여기에는 모든 객체가 가져야 할 공통 메서드들이 정의됩니다.
Leaf : 실제 작업을 수행하는 객체입니다. 트리 구조의 말단 노드에 해당하며, 더 이상 하위 객체를 가지지 않습니다.
Composite : 다른 컴포넌트(Leaf나 다른 Composite 객체)를 자식으로 가지는 복합 객체입니다. 자식 객체에 대한 작업을 수행하거나 자식 객체에 작업을 위임합니다.
복합체 패턴의 핵심은 재귀적 구조와 하위 객체에 대한 작업 위임입니다. 복합 객체는 하위 객체들을 순회하며 작업을 수행하고, 이러한 동작은 재귀적으로 이루어지기 때문에 복합 객체와 단일 객체를 동일한 방식으로 다룰 수 있습니다.
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
File System을 Composite 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();
Leaf
에 해당합니다. 각 파일은 크기(size
)와 이름(name
)을 가지고 있습니다.Composite
에 해당합니다. 이 상자는 다른 파일이나 다른 폴더를 담을 수 있습니다. 즉, 폴더 안에 폴더가 있을 수 있고, 그 안에 또 다른 파일이나 폴더가 있을 수 있습니다.위 코드 예제는 Composite
패턴을 활용하여 파일 시스템을 모델링하는 방법을 보여줍니다. 이 패턴을 통해 파일과 폴더를 동일한 방식으로 처리할 수 있으며, 폴더 내의 복잡한 계층 구조도 쉽게 관리할 수 있습니다.
Composite 패턴은 객체를 트리 구조로 구성하여 개별 객체(Leaf)와 복합 객체(Composite)를 동일하게 처리할 수 있도록 하는 강력한 디자인 패턴입니다. 이 패턴을 사용하면 복잡한 계층 구조를 단순화하고, 개별 객체와 객체 그룹을 동일한 인터페이스로 다룰 수 있어 코드의 유연성과 확장성을 크게 향상시킬 수 있습니다. 그러나 Composite 패턴은 구조가 복잡해질 수 있으며, 특히 트리 구조가 깊어질수록 객체 관리와 메서드 호출이 복잡해질 수 있습니다. 또한, 단일 객체와 복합 객체 간의 차이가 모호해질 수 있어 특정 상황에서는 명확한 구분이 어려울 수 있습니다.
https://inpa.tistory.com/entry/GOF-💠-복합체Composite-패턴-완벽-마스터하기