컴포지트 패턴
Last updated
Last updated
도메인을 트리 형태로 표현할 수 있고 전체를 포괄하는 최상위 객체가 나머지 하위 객체들을 관리하는 형태일 때, 모든 객체들에 동일한 행동을 갖는 메서드를 추가한다.
이를 통해 최상위 객체의 메서드에서는 나머지 하위 객체의 메서드들을 호출한 결과를 종합해 반환하면 된다.
객체를 트리 구조로 구성해 부분-전체 계층 구조를 구현한다. 클라이언트에서 개별 객체와 복합 객체를 같은 방법으로 다룰 수 있다.
부분-전체 계층 구조를 생성하여 부분들이 계층을 이루고 있지만 모든 부분을 묶어서 전체를 다룰 수 있도록 한다.
구성 요소는 복합 객체이거나 리프(Leaf) 객체이다. 복합(Composite) 객체에는 여러 구성 요소(Component)들을 담을 수 있다. 루트 구성 요소가 가장 위에 있고 점점 아래로 계층을 이루는 트리 모양이다.
예를 들어 Menu 라는 복합 객체에는 구성 요소인 MenuItem들과 또 다른 복합 객체인 Menu가 존재할 수 있는 형태이다.
복합 객체와 리프 객체는 인터페이스가 동일해야 하며 이 덕분에 클라이언트는 복합 객체든 리프 객체든 똑같은 명령을 호출할 수 있다.
단, 인터페이스를 동일하게 하다보니 의미 없는 메서드가 생길 수 있다. 보통 이 경우 아무 것도 하지 않거나 null, false, UnsupportedException 등을 반환한다.
재귀적인 합성을 통해 여러 객체들을 정리한다는 관점에서 데코레이터 패턴과 유사하다. 데코레이터 패턴은 래핑된 객체에 책임을 추가해 확장하는 반면 컴포지트 패턴은 래핑된 객체들을 요약하기만 한다.
장점
투명성을 확보하여 자식들을 관리하는 기능과 리프 노드로의 기능을 하나의 클래스에 담아 클라이언트가 복합 객체와 리프 노드를 같은 방식으로 다룰 수 있도록 한다.
클라이언트는 복합 객체를 사용하는지 리프 객체를 사용하고 있는지 신경쓰지 않고 메소드 하나만 호출하면 전체 구조를 조회할 수 있다.
객체들을 모아서 관리할 때 유용하다.
단점
단일 책임 원칙을 위반한다.
기능이 너무 다른 클래스들에는 공통 인터페이스를 제공하기 어려울 수 있으며, 컴포넌트 인터페이스를 과도하게 일반화하여 이해하기 어렵게 만들 수 있다.
필요한 메서드들을 모두 Component라는 추상 클래스 혹은 인터페이스에 정의해둔다.
Component를 상속받는 Leaf, Composite 클래스를 정의한다. 각 구현 클래스에서는 반드시 지원해야 하는 메서드만 정의한다.
아침 메뉴판, 점심 메뉴판, 저녁 메뉴판이 각각 있고 점심 메뉴판에는 디저트 메뉴판이, 저녁 메뉴판에는 술 메뉴판이 포함되는 것을 표현하려면 컴포지트 패턴을 사용해야 한다.
먼저 추상 클래스를 선언해 복합 객체와 리프 객체를 공통화한다. 공통적으로 처리할 수 있는 메서드는 구현해두고 처리할 수 없으면 UnsupportedOperationException를 반환하도록 해둔다. 혹은 인터페이스로 두어도 된다.
리프 객체가 될 수 있는 클래스를 구현한다. 하위 메뉴가 없고 메뉴의 특징, 설명을 담는다.
점심 메뉴인 엔초비파스타나 디저트 메뉴인 브라우니가 이 메뉴 아이템에 해당한다.
복합 객체를 나타내는 클래스로, 하위 메뉴가 존재할 수 있다.
이 클래스를 통해 메뉴판 객체를 만들 수 있으며 아침 메뉴판에는 메뉴들만 있고, 점심 메뉴판에는 메뉴들과 함께 디저트 메뉴판이 들어갈 수 있다.
사용자가 하나의 최상위 메뉴 컴포넌트에 print()
메서드를 호출하면, 내부적으로 하위 컴포넌트들을 순회하여 모든 메뉴가 출력된다.