티스토리 뷰

컴포지트 패턴이란?

복합 객체(Composite) 와 단일 객체(Leaf)를 동일한 컴포넌트로 취급하여, 클라이언트에게 이 둘을 구분하지 않고 동일한 인터페이스를 사용하도록 하는 구조 패턴입니다. 컴포지트 패턴은 전체-부분의 관계를 갖는 객체들 사이의 관계를 트리 계층 구조로 정의해야 할때 유용합니다.

컴포지트 패턴 사용 방법

  • Component : Leaf와 Compsite 를 묶는 공통적인 상위 인터페이스
  • Composite : 복합 객체로서, Leaf 역할이나 Composite 역할을 넣어 관리하는 역할을 한다.  
    • Component 구현체들을 내부 리스트로 관리한다
    • add 와 remove 메소드는 내부 리스트에 단일 / 복합 객체를 저장
    • Component 인터페이스의 구현 메서드인 operation은 복합 객체에서 호출되면 재귀 하여, 추가 단일 객체를 저장한 하위 복합 객체를 순회하게 된다.
  • Leaf: 단일 객체로서, 단순하게 내용물을 표시하는 역할을 한다.
    • Component 인터페이스의 구현 메서드인 operation은 단일 객체에서 호출되면 적절한 값만 반환한다
  • Client : 클라이언트는 Component를 참조하여 단일 / 복합 객체를 하나의 객체로서 다룬다.

클라이언트에서 operation 메서드를 호출하게 되면, 단일체일 경우 값이 호출 되고, 복합체일 경우 자기 자신을 호출하는 재귀 함수에 의해 저장하고 있는 하위 Leaf 객체들을 순회하여 호출

 

자식 객체들을 관리하는 함수들 선언 위치

For Uniformity

일관성을 추구하는 방식으로, 자식을 관리하기 위한 메소드들을 Composite가 아닌 Component에 정의하여 Client는 Leaf와 Composite를 일관되게 취급할 수 있다.
하지만 Client에서 Leaf 객체가 자식을 다루는 메서드를 호출할 수 있기 때문에, 타입에 대한 안정성을 잃게 된다.
interface Component {
    void operation();
    void add(Component c);
    void remove(Component c);
    List<Component> getChild();
}

class Leaf implements Component {

    @Override
    public void operation() {
        System.out.println(this + " 호출");
    }
}

class Composite implements Component {

    // Leaf 와 Composite 객체 모두를 저장하여 관리하는 내부 리스트
    List<Component> components = new ArrayList<>();

    @Override
    public void add(Component c) {
        components.add(c); // 리스트 추가
    }

    @Override
    public void remove(Component c) {
        components.remove(c); // 리스트 삭제
    }

    @Override
    public void operation() {
        System.out.println(this + " 호출");
        
        // 내부 리스트를 순회하여, 단일 Leaf이면 값을 출력하고,
        // 또다른 서브 복합 객체이면, 다시 그 내부를 순회하는 재귀 함수 동작이 된다.
        for (Component component : components) {
            component.operation(); // 자기 자신을 호출(재귀)
        }
    }
    
    @Override
    public List<Component> getChild() {
        return components;
    }
}
class Client {
    public static void main(String[] args) {
        // 1. 최상위 복합체 생성
        Component composite1 = new Composite();

        // 2. 최상위 복합체에 저장할 Leaf와 또다른 서브 복합체 생성
        Component leaf1 = new Leaf();
        Component composite2 = new Composite();

        // 3. 최상위 복합체에 개체들을 등록
        composite1.add(leaf1);
        composite1.add(composite2);

        // 4. 서브 복합체에 저장할 Leaf 생성
        Component leaf2 = new Leaf();
        Component leaf3 = new Leaf();
        Component leaf4 = new Leaf();

        // 5. 서브 복합체에 개체들을 등록
        composite2.add(leaf2);
        composite2.add(leaf3);
        composite2.add(leaf4);

        // 6. 최상위 복합체의 모든 자식 노드들을 출력
        composite1.operation();
    }
}

 


For Type Safety

타입의 안정성을 추구하는 방식이다.
이 방식은 자식을 관리하기 위한 add(), remove()와 같은 메서드들이 오직 Composite에만 정의되었다.
이로 인해 Client는 Leaf와 Composite를 다르게 취급한다.
하지만 Client에서 Leaf 객체가 자식을 다루는 메서드를 호출할 수 없기 때문에, 타입에 대한 안정성을 얻게 된다.
interface Component {
    void operation();
}

class Leaf implements Component {

    @Override
    public void operation() {
        System.out.println(this + " 호출");
    }
}

class Composite implements Component {

    // Leaf 와 Composite 객체 모두를 저장하여 관리하는 내부 리스트
    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(this + " 호출");
        
        // 내부 리스트를 순회하여, 단일 Leaf이면 값을 출력하고,
        // 또다른 서브 복합 객체이면, 다시 그 내부를 순회하는 재귀 함수 동작이 된다.
        for (Component component : components) {
            component.operation(); // 자기 자신을 호출(재귀)
        }
    }
    
    public List<Component> getChild() {
        return components;
    }
}
class Client {
    public static void main(String[] args) {
        // 1. 최상위 복합체 생성
        Composite composite1 = new Composite();

        // 2. 최상위 복합체에 저장할 Leaf와 또다른 서브 복합체 생성
        Leaf leaf1 = new Leaf();
        Composite composite2 = new Composite();

        // 3. 최상위 복합체에 개체들을 등록
        composite1.add(leaf1);
        composite1.add(composite2);

        // 4. 서브 복합체에 저장할 Leaf 생성
        Leaf leaf2 = new Leaf();
        Leaf leaf3 = new Leaf();
        Leaf leaf4 = new Leaf();

        // 5. 서브 복합체에 개체들을 등록
        composite2.add(leaf2);
        composite2.add(leaf3);
        composite2.add(leaf4);

        // 6. 최상위 복합체의 모든 자식 노드들을 출력
        composite1.operation();
    }
}

 


패턴 사용시기

  • 전체-부분 관계를 트리 구조로 표현하고 싶을 때
  • 전체-부분 관계를 클라이언트에서 부분, 관계 객체를 균일하게 처리하고 싶을 때

패턴 장점

  • 단일체와 복합체를 동일하게 여기기 때문에 묶어서 연산하거나 관리할 때 편리하다.
  • 다형성 재귀를 통해 복잡한 트리 구조를 보다 편리하게 구성 할 수 있다. 
  • 수평적, 수직적 모든 방향으로 객체를 확장할 수 있다.
  • 새로운 Leaf 클래스를 추가하더라도 클라이언트는 추상화된 인터페이스 만을 바라보기 때문에 개방 폐쇄 원칙(OCP)Visit Website을 준수 한다. (단일 부분의 확장이 용이)

패턴 단점

  • 재귀 호출 특징 상 트리의 깊이(depth)가 깊어질 수록 디버깅에 어려움이 생긴다.
  • 설계가 지나치게 범용성을 갖기 때문에 새로운 요소를 추가할 때 복합 객체에서 구성 요소에 제약을 갖기 힘들다.
  • 예를들어, 계층형 구조에서 leaf 객체와 composite 객체들을 모두 동일한 인터페이스로 다루어야하는데, 이 공통 인터페이스 설계가 까다로울 수 있다.
    • 복합 객체가 가지는 부분 객체의 종류를 제한할 필요가 있을 때
    • 수평적 방향으로만 확장이 가능하도록 Leaf를 제한하는 Composite를 만들때

참고

 

💠 복합체(Composite) 패턴 - 완벽 마스터하기

Composite Pattern 복합체 패턴(Composite Pattern)은 복합 객체(Composite) 와 단일 객체(Leaf)를 동일한 컴포넌트로 취급하여, 클라이언트에게 이 둘을 구분하지 않고 동일한 인터페이스를 사용하도록 하는

inpa.tistory.com

 

'CS > Design Pattern' 카테고리의 다른 글

옵서버 패턴 (Observer Pattern)  (0) 2024.04.18
싱글톤 패턴 (Singleton Pattern)  (0) 2024.04.18
브리지 패턴 (Bridge Pattern)  (0) 2024.04.17
퍼싸드 패턴 (Facade Pattern)  (0) 2024.04.17
어댑터 패턴 (Adaptor Pattern)  (0) 2024.04.03