Composite이란 사전적으로 혼합물이라는 뜻이다. 언뜻 다른 패턴들과는 다르게 이름만으로 직관적으로 그 역할을 유추하기가 다소 쉽지는 않지만, 이름 그대로 서로 다른 무엇인가를 혼합하여 하나인것처럼 취급하는 패턴이다. 보통 내용물과 그릇, 혹은 단수와 복수를 하나로 동일시한다고 이야기하는데 예시를 살펴보면 이해가 보다 빠르다.
Composite 패턴은 어떤 위계질서를 트리 형태로 나타낼 수 있는 상황에서 쓰이며 일상적으로 굉장히 자주 등장하는 패턴이다.
예를들어,
Composite 패턴을 이용해 파일 시스템을 간단하게 구현해보자.
Composite 패턴에는 다음의 객체들이 필요하다.
먼저 파일과 디렉토리의 공통 인터페이스를 나타내는 객체를 만든다.
abstract class Entry {
abstract getName(): string;
abstract getSize(): number;
printList(): void;
printList(prefix: string): void;
printList(prefix?: string) {
if (prefix === undefined) {
this.printListImpl('');
} else {
this.printListImpl(prefix);
}
}
protected abstract printListImpl(prefix: string): void;
toString(): string {
return `${this.getName()} (${this.getSize()})`;
}
}
Entry
클래스는 이름과 크기를 표시하는 getName
과 getSize
라는 인터페이스의 형태와, 해당 객체의 하위 내용물들을 모두 출력하는 printList
라는 메소드를 가진다.
Leaf는 내용물이 되는 단수 객체로 파일 시스템에서는 개별 파일이 이에 해당한다.
class File extends Entry {
constructor(
private readonly name: string,
private readonly size: number,
) {
super();
}
getName(): string {
return this.name;
}
getSize(): number {
return this.size;
}
printListImpl(prefix: string): void {
console.log(`${prefix}/${this.toString()}`);
}
}
File
은 그 안에 다른 내용물이나 그릇을 담을 수 없기 때문에 printListImpl
에서 오로지 자기자신만을 출력한다.
마지막으로 File
의 집합체가 될 Directory
를 만든다.
class Directory extends Entry {
private readonly directory: Entry[] = [];
constructor(
private readonly name: string,
) {
super();
}
getName(): string {
return this.name;
}
getSize(): number {
return this.directory.reduce((acc, entry) => acc + entry.getSize(), 0);
}
add(entry: Entry): Entry {
this.directory.push(entry);
return this;
}
printListImpl(prefix: string): void {
console.log(`${prefix}/${this.toString()}`);
this.directory.forEach((entry) => {
entry.printList(`${prefix}/${this.name}`);
});
}
}
내용물들을 담을 수 있는 그릇이기 때문에 내용물들을 추가하거나 관리하기 위한 메소드들이 정의되어야한다. 여기서는 add
라는 메소드가 추가되었다. 그리고 그릇이기 때문에 그릇 안에 포함된 하위 객체들을 출력할 수 있도록 printlistImpl
을 구현한다.
이제 구현한 코드의 동작을 확인해본다.
실제로 파일이나 디렉토리를 생성하지는 않겠지만, 디렉토리를 만들고 그 아래 다른 파일이나 디렉토리를 추가하고 출력을 해보자.
console.log('Making root entries...');
const rootdir = new Directory('root');
const bindir = new Directory('bin');
const tmpdir = new Directory('tmp');
const usrdir = new Directory('usr');
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File('vi', 10000));
bindir.add(new File('latex', 20000));
rootdir.printList();
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)
Java언어로 배우는 디자인 패턴 입문 - 쉽게 배우는 Gof의 23가지 디자인패턴 (영진닷컴)