복합체 패턴
- 객체들을 트리 구조들로 구성한 후, 이러한 구조들과 개별 객체들처럼 작업할 수 있도록 하는 구조 패턴
- 복합체 패턴은 앱의 핵심 모델이 트리로 표현될 수 있을 때만 사용
활용성
- 부분 - 전체의 객체 계통을 표현하고 싶을 때
- 사용자가 객체의 합성으로 생긴 복합 객체와 개개의 객체 사이의 차이를 알지않고도 자기 일을 할 수 있도록 만들고 싶을 때
구조
요소
- 컴포넌트(Component)
- 집합 관계에 정의될 모든 객체에 대한 인터페이스를 정의
- 리프(Leaf)
- 가장 말단의 객체, 즉 자식이 없는 객체, 인터페이스 구현
- 컴포지트(Composite)
- 자식이 있는 구성요소에 대한 행동을 정의, 자신이 복합하는 요소들을 저장하면서 인터페이스에 정의된 자식 관련 연산을 구현
- 클라이언트(Client)
- 인터페이스를 통해 복합 구조 내의 객체들을 조작
협력 방법
- 사용자는 Component 인터페이스를 사용한다
- 요청받은 대상이 Leaf 인스턴스면 자신이 정의한 행동을 직접 수행한다.
- 대상이 Composite이면 자식 객체들에게 요청을 위임한다.
장점
- 사용자의 코드가 단순해짐.
- 코드가 복합 구조이나 단일 객체와 동일하게 다루는 코드로 작성되기 때문에.(사용자는 복합구조인지 단일구조인지 모르기 때문)
- 개방폐쇄 원칙.
- 객체 트리와 작동하는 기존 코드를 훼손하지 않고 앱에 새로운 요소 유형들을 도입
단점
- 기능이 너무 다른 클래스들에는 공통 인터페이스를 제공하기 어려울 수 있으며, 어떤 경우에는 컴포넌트 인터페이스를 과도하게 일반화해야 하여 이해하기 어렵게 만들 수 있다.
예시 코드
protocol FileComponent {
func display()
func add(file: FileComponent)
}
extension FileComponent {
func add(file: FileComponent) { }
}
class File: FileComponent {
private var name: String
init(name: String) {
self.name = name
}
func display() {
print("File: \(name)")
}
}
class Directory: FileComponent {
private var name: String
private var files: [FileComponent] = []
init(name: String) {
self.name = name
}
func add(file: FileComponent) {
files.append(file)
}
func display() {
print("Directory: \(name)")
for file in files {
file.display()
}
}
}
let file1: FileComponent = File(name: "file1.txt")
let file2: FileComponent = File(name: "file2.txt")
let directory1: FileComponent = Directory(name: "Folder 1")
directory1.add(file: file1)
directory1.add(file: file2)
let file3: FileComponent = File(name: "file3.txt")
let directory2: FileComponent = Directory(name: "Folder 2")
directory2.add(file: file3)
let rootDirectory: FileComponent = Directory(name: "Root")
rootDirectory.add(file: directory1)
rootDirectory.add(file: directory2)
rootDirectory.display()
참고