안녕하세요! GoF 디자인 패턴 연재, 이번 시간에는 구조 패턴(Structural Patterns)의 한 종류로, 개별 객체와 객체의 집합을 동일한 방식으로 다룰 수 있게 해주는 컴포지트(Composite) 패턴에 대해 알아보겠습니다.
정의
컴포지트 패턴은 객체들을 트리 구조로 구성하여, '부분-전체' 계층을 표현하는 패턴입니다. 이 패턴을 사용하면 클라이언트는 개별 객체(Leaf)와 복합 객체(Composite)를 동일하게 취급할 수 있습니다.
가장 대표적인 예는 컴퓨터의 파일 시스템입니다.
- 파일(File) 은 개별 객체입니다.
- 폴더(Folder)는 다른 파일이나 또 다른 폴더들을 담을 수 있는 복합 객체입니다.
우리는 파일의 크기를 확인하거나 폴더의 전체 크기를 확인할 수 있습니다. 이때 폴더의 크기는 그 안에 포함된 모든 파일과 하위 폴더들의 크기를 합한 값이죠. 컴포지트 패턴을 사용하면, 클라이언트는 '파일'이든 '폴더'든 구분하지 않고 "크기를 알려줘"라는 동일한 요청을 보낼 수 있습니다. 이처럼 단일 객체와 객체 집합에 대해 동일한 인터페이스를 사용하게 하여 클라이언트 코드를 단순화하는 것이 이 패턴의 핵심입니다.
- Component (컴포넌트): Leaf와 Composite를 아우르는 공통 인터페이스입니다. 트리 내의 모든 객체들이 구현해야 할 메서드(예: operation())를 선언합니다. 또한, 자식을 관리하는 메서드( add(), remove() 등)를 선언할 수도 있습니다.)
- Leaf (리프): 트리의 가장 말단에 있는 개별 객체입니다. 자식을 가지지 않으며, Component 인터페이스의 메서드를 구현합니다. (예: File)
- Composite (컴포지트): 자식(Component)들을 가지는 복합 객체입니다. Component 인터페이스의 메서드를 구현하며, 보통 자신의 자식들에게 요청을 재귀적으로 전달하여 처리합니다. (예: Folder)
- Client (클라이언트): Component 인터페이스만을 사용하여 트리 구조의 객체들을 다룹니다.
사용예시
앞서 설명한 파일 시스템 예제를 자바(Java) 코드로 구현해 보겠습니다.
1. 컴포넌트 (Component) 인터페이스 정의
파일과 디렉토리(폴더)가 공통으로 가질 기능의 인터페이스를 정의합니다.
// Component 인터페이스
public interface FileSystemItem {
String getName();
int getSize(); // 파일 크기 또는 디렉토리 총 크기
void print(); // 계층 구조 출력
}
2. 리프 (Leaf) 클래스 구현
개별 객체인 File 클래스를 구현합니다.
// Leaf 클래스
public class File implements FileSystemItem {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() { return name; }
@Override
public int getSize() { return size; }
@Override
public void print() { System.out.println("- " + getName() + " (size: " + getSize() + "KB)"); }
}
3. 컴포지트 (Composite) 클래스 구현
다른 FileSystemItem들을 자식으로 가질 수 있는 Directory 클래스를 구현합니다.
import java.util.ArrayList;
import java.util.List;
// Composite 클래스
public class Directory implements FileSystemItem {
private String name;
private List<FileSystemItem> children = new ArrayList<>();
public Directory(String name) { this.name = name; }
public void add(FileSystemItem item) { children.add(item); }
public void remove(FileSystemItem item) { children.remove(item); }
@Override
public String getName() { return name; }
@Override
public int getSize() {
// 재귀적으로 모든 자식들의 크기를 합산하여 반환
int totalSize = 0;
for (FileSystemItem item : children) {
totalSize += item.getSize();
}
return totalSize;
}
@Override
public void print() {
System.out.println("D " + getName() + " (total size: " + getSize() + "KB)");
// 자식들을 순회하며 print() 호출
for (FileSystemItem item : children) {
item.print();
}
}
}
4. 클라이언트 (Client) 코드
클라이언트는 FileSystemItem 인터페이스만으로 파일과 디렉토리를 동일하게 다룹니다.
public class Client {
public static void main(String[] args) {
// 루트 디렉토리 생성
Directory root = new Directory("root");
// 파일 생성
File file1 = new File("file1.txt", 10);
File file2 = new File("file2.txt", 20);
// 하위 디렉토리 및 파일 생성
Directory subDir = new Directory("sub-dir");
File file3 = new File("file3.txt", 30);
subDir.add(file3);
// 루트에 추가
root.add(file1);
root.add(file2);
root.add(subDir);
// 전체 구조 출력
root.print();
// 실행 결과:
// D root (total size: 60KB)
// - file1.txt (size: 10KB)
// - file2.txt (size: 20KB)
// D sub-dir (total size: 30KB)
// - file3.txt (size: 30KB)
}
}
결론
- 객체들이 트리 구조를 형성하고, 이 구조를 일관된 방식으로 다루고 싶을 때 : UI 컴포넌트(패널 안에 버튼, 다른 패널 등이 포함되는 구조), 조직도, 파일 시스템 등
- 클라이언트 코드를 단순화하고 싶을 때 : 클라이언트는 개별 객체인지 복합 객체인지 신경 쓸 필요 없이 공통 인터페이스만 사용하면 되므로 코드가 간결해집니다.
- 새로운 종류의 Component를 쉽게 추가할 때 : 개방-폐쇄 원칙(OCP)을 잘 따르는 구조로, 새로운 Leaf나 Composite 클래스를 추가해도 기존 코드는 거의 영향을 받지 않습니다.
직관적이고 이해가 쉬운 구조를 가지고 있지만 다음과 같은 조건을 고려해야합니다
- 설계의 일반화 : 때로는 시스템을 너무 일반화시켜 인터페이스에 특정 클래스에만 적용되는 새로운 기능을 추가하기 어려울 수 있습니다.
- 자식 관리 메서드의 위치: Component 인터페이스에 자식을 추가/삭제하는 add(), remove() 메서드를 넣으면 Leaf 클래스는 이 메서드들을 불필요하게 구현해야 합니다. (예제처럼 예외를 던지거나 아무것도 하지 않도록 구현). 반대로 Composite 클래스에만 넣으면 클라이언트가 Composite와 Leaf를 구분해서 사용해야 하므로 투명성이 깨집니다. 이는 설계 시 트레이드오프 관계에 있습니다.
컴포지트 패턴은 복잡한 트리 구조를 단순하고 우아하게 처리할 수 있는 강력한 도구입니다. 부분과 전체를 동일하게 다루는 아이디어를 잘 기억해두시면 다양한 상황에서 유용하게 활용할 수 있습니다. 기술사공부의 키워드는 부분-전체 / 트리구조 / Lead-Composite 가 있겠네요 예제의 코드가 제일 이해가 쉬울 거 같습니다. 다음으로는 Decorator 패턴에 대해서 알아보겠습니다
'정보보안-이론 > XX에 대하여' 카테고리의 다른 글
[디자인 패턴] (GoF 구조패턴) Flyweight Pattern에 대하여 (0) | 2025.07.08 |
---|---|
[디자인 패턴] (GoF 구조패턴) Decorator Pattern에 대하여 (0) | 2025.07.07 |
[디자인 패턴] (GoF 구조패턴) Bridge Pattern에 대하여 (0) | 2025.07.06 |
[디자인 패턴] (GoF 구조패턴) Adapter Pattern에 대하여 (0) | 2025.07.06 |
[디자인 패턴] (GoF 생성패턴) Singleton Pattern에 대하여 (0) | 2025.07.04 |