컴포지트 패턴(Composite Pattern)

bolee·2022년 9월 21일
post-thumbnail

컴포지트 패턴(Composite Pattern))이란?

컴포지트 패턴(Composite pattern)이란 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 하는 패턴이다.

컴포지트 패턴은 클라이언트가 복합 객체(group of object)나 단일 객체를 동일하게 취급하는 것이 목적이다.
여기서 컴포지트의 의도는 트리 구조로 작성하여, 전체-부분(whole-part) 관계를 표현하는 것이다.
말하자면 컴포지트 패턴은 구조 패턴으로, 객체를 트리 구조로 관리할 수 있도록 해준다.

구성 요소

일반적인 컴포지트 패턴의 UML 다이어그램은 아래와 같다.

Client 클래스는 LeafComposite 클래스를 직접 참조하지 않고, 공통 인터페이스 Component 를 참조한다.
Leaf 클래스는 Component 인터페이스를 구현한다.
Composite 클래스는 Component 객체 자식들을 유지하고, operation() 과 같은 요청을 통해 자식들에게 전달한다.

  1. Component
    • 모든 component 들을 위한 추상화된 개념으로써, LeafComposite 클래스의 인터페이스이다.
  2. Leaf
    • Component 인터페이스를 구현한 클래스를 나타냄
  3. Composite
    • 내부에 다른 클래스 요소를 포함하고 있는 요소(Leaf를 포함하고 있는 요소)
    • Component 인터페이스를 구현하고, 구현되는 자식(Leaf or Composite) 들을 가진다
    • 이러한 자식들을 관리하기 위한 메소드(addChild, removeChild...)를 구현한다.
    • 일반적으로 인터페이스에 작성된 메소드는 자식에게 위임하는 처리를 한다.
  4. Client
    • Component를 통해 외부에서 메세지를 요청하는 객체

이를 기반으로 객체 다이어그램을 나타내면 아래와 같다.

Client에서 트리 구조에서의 top-level 에 존재하는 Composite1 에 요청을 보낸다.
그러면 Component 인터페이스를 구현한 객체들은 트리 구조를 토대로 위에서 아래 방향으로 모든 자식 요소에게 전달하게 된다.

구현 예시

원, 삼각형, 사각형 등과 같은 형태의 그래픽을 주제로한 구현 예시를 살펴보자

Component 인터페이스 정의

아래는 Component에 해당하는 인터페이스 Graphic을 나타낸다.
이 인터페이스는 출력하는 메서드를 가지고 있다.

/** Component */
interface Graphic {
	// Prints the graphic
    public void print();
}

Leaf 구현

Ellipse 클래스는 Leaf에 해당하는 클래스로 Component 인터페이스를 구현한다.

/** Leaf */
class Ellipse implements Graphic {
	//Prints the graphic
    public void print() {
    	System.out.println("Ellipse");
    }
}

Composite 구현

CompositeGraphic 클래스는 Composite에 해당하는 클래스로 Component에 해당하는 Graphic 인터페이스를 구현하는 요소들을 관리하기 위한 리스트가 존재한다.
그리고 이를 위해 추가적으로 add, remove 메소드가 존재하고 인터페이스에서 작성된 메소드인 print()를 오버라이딩 하였다.
여기서 print()Composite가 일반적으로 하는 형태로써, 자식들에게 요청을 위임하는 처리를 하게 된다.

/** Composite */
class CompositeGraphic implements Graphic {
	// Collection of child graphics
    private List<Graphic> childGraphics = new ArrayList<Graphic>();
    
    // Prints the graphic
    public void print() {
    	for (Graphic graphic : childGraphics) {
        	graphic.print();	// Delegation(위임)
        }
    }
    
    // Adds the graphic to the composition
    public void add(Graphic graphic) {
    	childGraphics.add(graphic);
    }
    
    // Remove the graphic from the composition
    public void remove(Graphic graphic) {
    	childGraphics.remove(graphic);
    }
}

Client 구현

Client에서는 Composite 는 자식을 관리하기 위한 메소드인 add()를 통해 자식으로 여러 개의 Leaf 를 가질 수 있다.
또한, Composite 에 해당하는 또 다른 인스턴스를 자식으로 가질 수 있다.
결과적으로, 트리 구조가 만들어지면서 print() 와 같이 단일 객체와 복합 객체가 같은 방법으로 처리되는 형태가 만들어진다.

/** Client */
public class Program {
	public static void main(String[] args) {
    	// Initialize four ellipses
        Ellipse ellipse1 = new Ellipse();
        Ellipse ellipse2 = new Ellipse();
        Ellipse ellipse3 = new Ellipse();
        Ellipse ellipse4 = new Ellipse();
        
        // Initialize three composite graphics
        CompositeGraphic graphic = new CompositeGraphic();
        CompositeGraphic graphic1 = new CompositeGraphic();
        CompositeGraphic graphic2 = new CompositeGraphic();
        
        // Composes the graphics
		graphic1.add(ellipse1); // children - leaf
        graphic1.add(ellipse2); // children - leaf
        graphic1.add(ellipse3); // children - leaf

        graphic2.add(ellipse4); // children - leaf

        graphic.add(graphic1); // children - composite
        graphic.add(graphic2); // children - composite

        //Prints the complete graphic (Four times the string "Ellipse").
        graphic.print();
    }
}

컴포지트 패턴의 2가지 방식

위 컴포지트 패턴에서 Composite 클래스는 자식들을 관리하기 위해 추가적인 메소드가 필요하였다.
이러한 메소드들이 어떻게 작성되느냐에 따라, 컴포지트 패턴은 다른 목적을 추구할 수 있다.

For Type Safety

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

Ellipse ellipse = new Ellipse();
CompositeGraphic graphic = new CompositeGraphic();

For Uniformity

일관성을 추구하는 방식이다.
자식을 관리하기 위한 메소드들을 Composite가 아닌 Component에 정의하여 ClientLeafComposite를 일관되게 취급할 수 있다.
하지만 Client에서 Leaf 객체가 자식을 다루는 메서드를 호출할 수 있기 때문에, 타입에 대한 안정성을 잃게 된다.

Graphic ellipse = new Ellipse();
Graphic graphic = new CompositeGraphic();

장점 및 단점

장점

  • 복잡한 트리 구조의 클래스들을 편하게 관리할 수 있다.
  • 기존의 코드를 수정하지 않고도 구조에 포함되는 새로운 클래스를 추가할 수 있다.

단점

  • 기능이 많은 클래스를 Component 인터페이스에 포함될 수 있도록 설계하는 것이 어려울 수 있다.
  • Component 인터페이스가 과도하게 추상화(일반화)되어 있어 다른 클래스를 포함하기 어려울 수도 있다.

활용 상황

복합 객체와 단일 객체의 처리 방법이 다르지 않을 경우, 전체-부분 관계로 정의할 수 있다.
전체-부분 관계의 대표적인 예는 Directory-File이 존재하며, 이러한 전체-부분 관계를 효율적으로 정의할 때 유용하다.

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

참고 자료

0개의 댓글