객체들의 관계를 트리구조로 구성하여 부분-전체 계층을 표현하는 패턴으로 사용자가 단일 객체(Leaf)와 복합 객체(Composite) 모두 동일하게 다루도록 하는 패턴이다.
즉, 복합 객체 그룹(전체)과 단일 객체를 동일하게 취급하거나 다룰 수 있게 해주는 방식으로, 재귀적 특성을 띄게 된다.
쉽게 설명하자면 윈도우나 리눅스의 파일 시스템 구조와 유사한 형태를 띄고있는데, root 디렉토리 안에 또 다른 디렉토리(Composite)가 있을수도, 파일(Leaf)이 있을 수도 있다.
복합체 패턴은 여기서 디렉토리와 파일을 동일하게 다룸으로써 구현을 단순화 시키는 것을 목적으로 한다. 이러한 구조에서 한 디렉토리에서 서브 디렉토리로 이동하게 되면 해당 디렉토리에는 파일이 있을 수도, 또 다른 서브 디렉토리가 있을 수도 있는데, 이런 복잡한 계층 구조에서 복합 객체를 재귀 함수를 이용해 하위 객체가 다루도록 위임한다. 그러면 복합 객체와 단일 객체를 대상으로 똑같은 작업을 적용할 수 있어 단일/복합 객체를 구분할 필요가 거의 없어진다.
- Component : Leaf와 Compsite 를 묶는 공통적인 상위 인터페이스
- Composite : 복합 객체로서, Leaf 역할이나 Composite 역할을 넣어 관리하는 역할을 한다.
Component 구현체들을 내부 리스트로 관리한다
add 와 remove 메소드는 내부 리스트에 단일 / 복합 객체를 저장
Component 인터페이스의 구현 메서드인 operation은 복합 객체에서 호출되면 재귀 하여, 추가 단일 객체를 저장한 하위 복합 객체를 순회하게 된다.
Leaf: 단일 객체로서, 단순하게 내용물을 표시하는 역할을 한다.
Component 인터페이스의 구현 메서드인 operation은 단일 객체에서 호출되면 적절한 값만 반환한다.- Client : 클라이언트는 Component를 참조하여 단일 / 복합 객체를 하나의 객체로서 다룬다.
복합체 패턴의 핵심은 Composite 와 Leaf가 동시에 구현하는 operation() 인터페이스 추상 메서드를 정의하고, Composite 객체의 operation() 메서드는 자기 자신을 호출하는 재귀 형태로 구현하는 것이다. 왜냐하면 폴더 안에 폴더를 넣고, 그 안에 또 폴더를 넣고 파일을 넣는 트리 구조를 생각해보면, 재귀적으로 반복되는 형식이 나타나기 때문이다. 그래서 단일체와 복합체를 동일한 개체로 취급하여 처리하기 위해 재귀 함수 원리를 이용한다.
// Component.java
public interface Component {
void operation();
}
// Leaf.java
public class Leaf implements Component {
@Override
public void operation() {
System.out.println("[Leaf] " + this + " has been called");
}
}
// Composite.java
public class Composite implements Component {
private List<Component> components = new ArrayList<>();
public void add(Component c) {
components.add(c);
}
public void remove(Component c) {
components.remove(c);
}
@Override
public void operation() {
System.out.println("[Composite] " + this + " has been called");
for (Component c : components) {
c.operation();
}
}
public Component getChild(int no) {
return components.get(no);
}
public List<Component> getChildren() {
return components;
}
}
위와 같이 Component를 interface로 구현하여 operation 메서드를 추상 메서드로 선언해준다. 그리고 Leaf와 Composite에서 각각 Leaf는 하위 객체가 없으므로 본인 호출, Composite는 본인 호출 후 하위 객체(Component)를 다시 재귀적으로 호출하도록 구현해준다.
// Client.java
public class Client {
public static void main(String[] args) {
Composite c1 = new Composite();
Leaf l1 = new Leaf();
System.out.println("------- execution 1 -------");
c1.operation();
l1.operation();
Composite c2 = new Composite();
Leaf l2 = new Leaf();
Leaf l3 = new Leaf();
Leaf l4 = new Leaf();
c1.add(l1);
c1.add(c2);
c2.add(l2);
c2.add(l3);
c2.add(l4);
System.out.println("------- execution 2 -------");
c1.operation();
}
}
// 실행 결과
------- execution 1 -------
[Composite] Composite@15db9742 has been called
[Leaf] Leaf@6d06d69c has been called
------- execution 2 -------
[Composite] Composite@15db9742 has been called
[Leaf] Leaf@6d06d69c has been called
[Composite] Composite@7852e922 has been called
[Leaf] Leaf@4e25154f has been called
[Leaf] Leaf@70dea4e has been called
[Leaf] Leaf@5c647e05 has been called