객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 함
위의 다이어그램의 각각의 역할은 다음과 같다.
Component → 모든 객체(부분, 전체)의 추상 인터페이스
공통적인 기능을 선언
Leaf → Component 인터페이스의 단일 객체 구현체
Composite → Component 인터페이스의 전체 관리 객체 구현체
Leaf와 똑같은 Component의 구현체이지만 Composite은 Leaf들을 관리하는 기능(add, remove)이 있다.
컴포짓 패턴은 전체(composite)-부분(Leaf) 관계(Ex: Directory-File)를 갖는 객체들 사이의 관계 정의할 때 사용 → 즉 클라이언트에서 composite 구현체를 이용해 Leaf를 관리하는 것인데 이것을 클라이언트는 Component라는 추상 인터페이스로만 기능을 사용하면 된다.
Department → Composite (어찌보면 Composite Of Composite)
ITLaboratory → Composite
PlanDepartment → Composite
Developer → Leaf
Planner → Leaf
위의 코드를 해석하면 부서와 직원은 모두 Saramin에 소속되어 있다. 그리고 직원은 개발자 ,기획자가 있고 개발자는 IT연구소 소속이고 기획자는 기획부서 소속이다.
클라이언트에서는 Leaf, Composite를 구별하지 않고 Saramin이라는 인터페이스의 추상 기능만으로 사용할 수 있다. 만약 추후 IT연구소에 다른 직원군이 생겨도 Saramin을 상속하고 구현만 하면 되고 클라이언트 코드는 변경될 필요가 없기 때문에 OCP를 만족하게 된다.
2. 장점과 단점
장점
복잡한 트리 구조를 편하게 사용할 수 있습니다.
클라이언트는 Component의 getPrice 메서드만 사용하면 되기 때문
다형성과 재귀를 활용할 수 있습니다.
하나의 getPrice 메서드가 구현체 마다 다르게 동작하는 다형성과 Leaf 객체를 찾기 위해 DFS와 같은 재귀를 활용하게 됩니다.
클라이언트 코드를 변경하지 않고 새로운 구현체를 추가할 수 있습니다.
컴포짓 패턴을 사용함으로써 OCP(Open-Closed Principle) 즉, 개방 폐쇄 원칙을 지키면서 프로그래밍을 할 수 있다는 것을 알 수 있습니다.
단점
트리를 만들야 하기 때문에 (공통된 인터페이스를 정의해야 하기 때문에) 지나치게 일반화 해야 하는 경우가 생길 수 있습니다.
예를 들어, 가격이 존재하지 않는 객체가 있을 수도 있는데, 이 객체는 getPrice가 굳이 필요하지 않지만 가방에 넣으려면 Component를 상속받아야 하기 때문에 지나친 일반화가 발생하는 경우라고 할 수 있습니다.
컴포짓 패턴을 적용하다가 억지로 일반화해야하는 경우가 발생한다면, 해당 구조가 컴포짓 패턴으로 구현하는 게 맞는지 다시 한 번 생각해봐야 합니다.
3. 컴포짓 패턴을 사용하는 Swing 라이브러리
스윙(Swing)은 자바 언어에서 GUI의 구현하기 위해 제공되는 라이브러리입니다. 자바에서 추구하는 WORE(Wirte Once, Run Everywhere)을 구현하기 위해 JDK 1.2 버전부터 사용되었습니다.
importjavax.swing.*;publicclassSwingExample {publicstaticvoidmain(String[] args) {// 프레임을 만듬JFrame frame =newJFrame();// 텍스트 필드 박스를 만들고 프레임에 추가JTextField textField =newJTextField();textField.setBounds(200,200,200,40);frame.add(textField);// 버튼을 만들고 프레임에 추가JButton button =newJButton("click");button.setBounds(200,100,60,40);button.addActionListener(e ->textField.setText("Hello Swing"));frame.add(button);// 프레임 크기 설정 후 보여주기frame.setSize(600,400);frame.setLayout(null);frame.setVisible(true); }}
여기서 JFrame, JTextField, JButton은 컴포짓 패턴으로 이루어져 있습니다. 이 3개의 객체는 전부 Component 라는 추상 클래스를 상속받고 있습니다.
프레임의 add 메서드는 아래 처럼 되어 있는데, 위 예시의 Bag(Composite) 같은 Component를 상속하는 객체들을 리스트로 가지고 있는 것을 알 수 있습니다.
컴포짓 패턴에서 Composite 클래스는 자식들을 관리하기 위한 추가적인 메서드가 필요합니다. 이러한 메서드의 설계 방식에 따라 2가지 형태의 방식으로 나눌 수 있습니다.
안정성을 추구하는 방식
안정성을 추구하는 방식은 자식을 다루는 add(), remove() 와 같은 메소드들은 오직 Composite 만 정의되었다. 그로 인해, Client는 Leaf와 Composite을 다르게 취급하고 있습니다. 하지만 Client에서 Leaf객체가 자식을 다루는 메소드를 호출할 수 없기 때문에, 타입에 대한 안정성을 얻게 됩니다.
먼저 예시로 들었던, Bag과 Item을 생각하면 됩니다.
일관성을 추구하는 방식
일관성을 추구하는 방식은 자식을 다루는 메소드들을 Composite가 아닌 Component에 정의하는 방식입니다. 그로 인해, Client는 Leaf와 Composite를 일관되게 취급할 수 있습니다. 하지만 Client는 Leaf 객체가 자식을 다루는 메소드를 호출할 수 있기 때문에, 타입의 안정성을 잃게 됩니다.