본문 바로가기

ComputerScience/DesignPattern

[DesignPattern] 컴포지트 패턴(Composite Pattern)

컴포지트 패턴

  • 컴포지트 패턴(Composite Pattern)으로 객체를 트리구조로 구성해서 부분-전체 계층을 구현한다.

 
컴포지트 패턴을 사용하면 클라이언트에서 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있습니다.
부분-전체 계층 구조(part-whole hierarchy)란, 부분들이 계층을 이루고 있지만 모든 부분을 묶어서 전체로 다룰 수 있는 구조를 뜻합니다. (트리에서 부모 노드와 자식 노드들의 가장 작은 부분들이 합쳐져서 트리의 전체 구조가 된다는 것을 생각하면 이해하기에 쉽다.)

트리와 구분되는 점은 트리에선 모든 Leaf 노드가 부모 노드가 될 수 있지만, 컴포지트 패턴에서는 Composite 객체만 Leaf 객체들을 관리하는 부모 노드가 될 수 있다는 것입니다.
 
컴포지트 패턴의 구성에는 다음과 같습니다.
 
 

Client 

클라이언트는 Component 인터페이스를 사용해서 복합 객체 내의 객체들을 조작할 수 있다.
 


Component

복합 객체 내에 들어있는 모든 객체의 인터페이스를 정의한다. 즉, 복합 노드와 잎에 관한 메소드까지 정의한다.

 
Composite(복합 객체)

Composite에서 Leaf들을 관리하는 기능을 구현해야한다. 그런 기능들이 복합 객체에게 별 쓸모가 없다면 예외를 던지는 방법으로 처리해도 된다. 자식이 있는 구성요소의 행동을 정의하고 자식 구성 요소를 저장하는 역할을 맡는다. 즉, 실질적인 부모 노드가 된다.

 
Leaf

자식을 갖지 않는다. getChild() 등의 메소드는 필요가 없다. (UnSupportedOperation 오류를 내거나 비워둔다.) Leaf는 그 안에 들어있는 원소의 행동을 정의한다.
 

이미지 출처: https://en.wikipedia.org/wiki/Composite_pattern

 
위의 UML Class Diagram 에서, Client 클래스는 Leaf 및 Composite 클래스를 직접 참조하지 않습니다.
대신에 Client는 공통적인 Component 인터페이스를 참조하며, Leaf와 Composite를 동일하게 다룰 수 있습니다.
Leaf 클래스는 자식이 없으며 Component 인터페이스를 직접 구현합니다.
Composite 클래스는 자식 Component 객체(자식)의 컨테이너를 유지하며 이러한 자식에 대한 요청을 전달합니다(각각의 자식에 대해: child.operation()).

Object Collaboration Diagram은 실행 시 상호작용을 보여줍니다.
이 예제에서 Client 객체는 트리 구조의 최상위 Composite 객체(Component 타입)에 요청을 보냅니다. 이 요청은 트리 구조 아래의 모든 자식 Component 객체(Leaf 및 Composite 객체)에게 전달(수행)됩니다.
 
예시를 봅시다.

이미지 출처: https://github.com/IT-Book-Organization/HeadFirst-DesignPattern/blob/main/Chapter_09/README.md

다음과 같은 구성을 갖는 복합 객체들이 있습니다.
이 때 코드는 다음과 같이 구현될 수 있습니다.
 

Component: MenuComponent

public abstract class MenuComponent {
   
	public void add(MenuComponent menuComponent) {
		throw new UnsupportedOperationException();
	}
	public void remove(MenuComponent menuComponent) {
		throw new UnsupportedOperationException();
	}
	public MenuComponent getChild(int i) {
		throw new UnsupportedOperationException();
	}
  
	public String getName() {
		throw new UnsupportedOperationException();
	}
	public String getDescription() {
		throw new UnsupportedOperationException();
	}
	public double getPrice() {
		throw new UnsupportedOperationException();
	}
	public boolean isVegetarian() {
		throw new UnsupportedOperationException();
	}
  
	public void print() {
		throw new UnsupportedOperationException();
	}
}

Leaf: MenuItem

public class MenuItem extends MenuComponent {
	String name;
	String description;
	boolean vegetarian;
	double price;
    
	public MenuItem(String name, 
	                String description, 
	                boolean vegetarian, 
	                double price) 
	{ 
		this.name = name;
		this.description = description;
		this.vegetarian = vegetarian;
		this.price = price;
	}
  
	public String getName() {
		return name;
	}
  
	public String getDescription() {
		return description;
	}
  
	public double getPrice() {
		return price;
	}
  
	public boolean isVegetarian() {
		return vegetarian;
	}
  
	public void print() {
		System.out.print("  " + getName());
		if (isVegetarian()) {
			System.out.print("(v)");
		}
		System.out.println(", " + getPrice());
		System.out.println("     -- " + getDescription());
	}
}

Composite: Menu

import java.util.Iterator;
import java.util.ArrayList;

public class Menu extends MenuComponent {
	ArrayList<MenuComponent> menuComponents = new ArrayList<MenuComponent>();
	String name;
	String description;
  
	public Menu(String name, String description) {
		this.name = name;
		this.description = description;
	}
 
	public void add(MenuComponent menuComponent) {
		menuComponents.add(menuComponent);
	}
 
	public void remove(MenuComponent menuComponent) {
		menuComponents.remove(menuComponent);
	}
 
	public MenuComponent getChild(int i) {
		return (MenuComponent)menuComponents.get(i);
	}
 
	public String getName() {
		return name;
	}
 
	public String getDescription() {
		return description;
	}
 
	public void print() {
		System.out.print("\n" + getName());
		System.out.println(", " + getDescription());
		System.out.println("---------------------");
  
		Iterator<MenuComponent> iterator = menuComponents.iterator();
		while (iterator.hasNext()) {
			MenuComponent menuComponent = 
				(MenuComponent)iterator.next();
			menuComponent.print();
		}
	}
}

Client: Waitress

public class Waitress {
	MenuComponent allMenus;
 
	public Waitress(MenuComponent allMenus) {
		this.allMenus = allMenus;
	}
 
	public void printMenu() {
		allMenus.print();
	}
}

클라이언트는 가장 최상위 루트 Composite 객체만 알고 있어도 재귀 구조를 통해 모든 Leaf 객체와 Composite 객체를 탐색할 수 있습니다.

 
Test Code

public class MenuTestDrive {
	public static void main(String args[]) {
		MenuComponent pancakeHouseMenu = 
			new Menu("PANCAKE HOUSE MENU", "Breakfast");
		MenuComponent dinerMenu = 
			new Menu("DINER MENU", "Lunch");
		MenuComponent cafeMenu = 
			new Menu("CAFE MENU", "Dinner");
		MenuComponent dessertMenu = 
			new Menu("DESSERT MENU", "Dessert of course!");
		MenuComponent coffeeMenu = new Menu("COFFEE MENU", "Stuff to go with your afternoon coffee");
  
		MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined"); // 루트 복합 객체
  
		allMenus.add(pancakeHouseMenu); // 복합 객체
		allMenus.add(dinerMenu); // 복합 객체
		allMenus.add(cafeMenu); // 복합 객체
		
		dinerMenu.add(new MenuItem(
			"Pasta",
			"Spaghetti with marinara sauce, and a slice of sourdough bread",
			true, 
			3.89));
   
		dinerMenu.add(dessertMenu); // 복합 객체

		dessertMenu.add(new MenuItem(
			"Apple Pie",
			"Apple pie with a flakey crust, topped with vanilla icecream",
			true,
			1.59)); // desserMenu에 더해지는 객체는 Leaf 객체, MenuItem은 Leaf 객체다.

		Waitress waitress = new Waitress(allMenus);
   
		waitress.printMenu();

 

투명성(transparency)

Component 인터페이스에서 Leaf와 Composite 기능을 전부 넣어서 클라이언트가 Composite객체와 Leaf 객체를 똑같은 방식으로 처리하게 만들어 클라이언트 입장에서 어떤 원소가 복합 객체인지 잎인지 투명하게 보이게 하는 것.

컴포지트 패턴에서는 Component를 상속 받는 객체들은 모두 Leaf와 Composite 두가지 역할을 가지고 있으므로 단일 책임 원칙을 깨고 있습니다.

대신 이 패턴에서는 투명성을 확보합니다.

참고 자료

https://en.wikipedia.org/wiki/Composite_pattern

Composite pattern - Wikipedia

From Wikipedia, the free encyclopedia Design pattern in software engineering In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes a group of objects that are treated the same way as a single insta

en.wikipedia.org

https://github.com/IT-Book-Organization/HeadFirst-DesignPattern/blob/main/Chapter_09/README.md