본문 바로가기
CS/디자인 패턴

컴포지트 패턴 (Composite pattern)

by cornsilk-tea 2023. 8. 22.

디자인 패턴 중 하나인 컴포지트 패턴에 대해 알아보려 한다.

우선 위키에 뭐라고 설명하고 있는지 한번 보자.

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 instance of the same type of object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. - 위키 -

쉽게 보자면 컴포지트 패턴이란, 한 오브젝트의 그룹과 그 오브젝트의 싱글 인스턴스가 같은 타입으로 취급되는 패턴이라고 할 수 있다. 또한 컴포지트 패턴을 통해서 오브젝트들을 트리 구조로 구성할 수 있다고 한다.

인스턴스: 클래스를 기반으로 생성된 실제 객체

그럼 이를 기반으로 컴포지트 패턴에 대해 하나씩 알아보자.


하나의 오브젝트와 오브젝트가 들어있는 그룹을 같게 취급한다?

그림을 통해 이해해 보도록 하자.

하나의 오브젝트 인스턴스와 오브젝트 그룹을 같게 취급한다는 말을 그림으로 나타내면 위와 같다.

오른쪽 그룹을 보면 오브젝트가 들어가는 리스트가 들어가 있는데, 이 안에 오브젝트가 들어감으로써 오른쪽이 오브젝트 그룹이 되는 거다.

그리고 이 둘을 같은 타입으로 취급한다는 것은 같은 인터페이스를 가지고 있는 것이라고 할 수 있다.

그림처럼 오브젝트와 오브젝트 그룹의 인터페이스 클래스를 정의해서 이 둘을 상속시키는 방식으로 클래스의 구조를 짤 수 있다.

이 구조를 컴포지트 패턴의 그림으로 변환하면 다음과 같다.

컴포지트 패턴에서

베이스 인터페이스를 컴포넌트

인터페이스를 상속받는 객체를 리프

인터페이스를 상속받는 그룹을 컴포지트라고 부른다.

컴포지트 안에는 오브젝트의 리스트가 들어가 있는데, 이 안에는 리프와 컴포지트의 베이스 컴포넌트가 들어간다.
즉, 컴포지트 안에는 컴포넌트를 상속받는 리프와 컴포지트 둘 다 들어갈 수 있는 것이다.

여기서 일반 클래스의 이름이 리프로 되어있는데, 맨 위에서 말했던 것처럼 컴포지트 패턴이 트리 구조를 구현할 때 좋기 때문이다.

아래 그림을 통해 예를 보자.

왼쪽 그림은 리프 2개가 묶이고, 그 가지가 다시 리프 한 개와 묶인 트리의 형태를 표현한 것이다.

그리고 이 구조를 컴포지트 패턴의 구조로 나타낸 그림이 오른쪽 그림이다.

하나의 컴포지트가 리프와 컴포지트를 가지고 있다.

그리고 그 컴포지트는 2개의 리프를 가지고 있다.

그럼 이 구조를 코드로 표현해 보겠다.

코드작성

1. Component(Component 인터페이스)

interface Component {
    void fn();
}
  • leaf와 Composite에 대한 공통된 인터페이스
  • fn() 메서드를 정의

2. Leaf(Leaf 클래스)

class Leaf implements Component {
    @Override
    public void fn() {
        System.out.println("leaf");
    }
}

 

  • fn() 메서드를 오버라이드하여 "leaf"를 출력

3. Composite(Composite 클래스)

class Composite implements Component {
    private List<Component> components = new ArrayList<>();

    public void add(Component component) {
        components.add(component);
    }

    @Override
    public void fn() {
        System.out.println("composite");
        for(Component component : components) {
            component.fn();
        }
    }
}
  • 컴포넌트 리스트를 가지고 있다.
  • fn() 메서드를 오버라이드하여 "composite"을 출력하고, 그 후 포함된 모든 구성요소에 대해 fn() 메서드를 호출

이제 다음 그림에 맞게 객체를 만들어보자.

 

    public static void main(String[] args) {
        Composite compst1 = new Composite();
        compst1.add(new Leaf());
        compst1.add(new Leaf());

        Composite compst0 = new Composite();
        compst0.add(new Leaf());
        compst0.add(compst1);

        compst0.fn();
    }

컴포지트 1을 생성한 후, 그 안에 두 개의 리프를 삽입한다.

컴포지트 0을 생성한 후 컴포지트 1과 새로운 리프를 삽입한다.

이제 컴포지트 0의 fn() 함수를 실행한 결과를 확인해 보자.

composite
leaf
composite
leaf
leaf

이처럼 컴포지트 0의 fn을 한번 호출하면 그 안에 들어있는 모든 오브젝트들에게 함수 fn이 퍼지는 것을 확인할 수 있다.

실행 흐름
compst0의 fn() 메서드를 호출하면 composite을 출력
그 안에 포함된 모든 구성요소에 대해 fn() 메서드를 호출
leaf가 출력되고, 다음으로 compst1의 fn() 메서드가 호출
compst1 역시 composite을 출력하고 그 안에 포함된 두 개의 Leaf에 대해 fn() 메서드를 호출하여 leaf를 두 번 출력

이런 방식으로 컴포지트 패턴은 트리구조의 복잡한 객체도 간단하게 다룰 수 있게 해 준다.

여기서 컴포지트 패턴의 장점을 확인할 수 있는데, 트리 구조가 아주 복잡할 때, 그 트리의 루트에서 함수 하나만 실행하면, 패턴의 특성에 따라 리프까지 자동으로 함수를 콜 해준다는 것이다.

코드작성 2

그럼 우리에게 보다 익숙한 예시를 사용하여 컴포지트 패턴을 다시 만들어보자.

이번에 만들어볼 컴포지트 패턴은 다음과 같다.

고양이 3마리를 묶어서 하나의 고양이 그룹을 만든다.

강아지 2마리를 묶어서 하나의 강아지 그룹을 만든다.

이 두 개의 그룹을 묶은 동물원 오브젝트를 만든다.

 

1. Component (Animal 인터페이스)

interface Animal {
    void speak();
}

2. Leaf (Cat 및 Dog 클래스)

class Cat implements Animal {
    @Override
    public void speak() {
        System.out.println("야옹");
    }
}

class Dog implements Animal {
    @Override
    public void speak() {
        System.out.println("멍멍");
    }
}

3. Composite (AnimalGroup 클래스)

class AnimalGroup implements Animal {
    private List<Animal> animals = new ArrayList<>();

    public void add(Animal animal) {
        animals.add(animal);
    }

    @Override
    public void speak() {
        System.out.println("group speaking..");
        for (Animal animal : animals) {
            animal.speak();
        }
    }
}

4. 호출

public static void main(String[] args) {
        AnimalGroup catGroup = new AnimalGroup();
        catGroup.add(new Cat());
        catGroup.add(new Cat());
        catGroup.add(new Cat());

        AnimalGroup dogGroup = new AnimalGroup();
        dogGroup.add(new Dog());
        dogGroup.add(new Dog());

        AnimalGroup zoo = new AnimalGroup();
        zoo.add(catGroup);
        zoo.add(dogGroup);

        zoo.speak();
}

main 메서드에서는 catGroup과 dogGroup이라는 두 개의 동물 그룹을 생성한다.

각 그룹에는 각각 3마리의 고양이와 2마리의 개가 추가된다.

그 후, zoo라는 큰 동물 그룹을 만들어 이전에 생성한 두 그룹을 추가한다.

그 후, speak() 메서드를 실행하면

group speaking..
group speaking..
야옹
야옹
야옹
group speaking..
멍멍
멍멍

이와 같이 모든 동물 그룹과 동물들이 순서대로 소리 내는 것을 확인할 수 있다.

컴포지트 패턴의 장점

위 예제들을 통해 알 수 있는 컴포지트 패턴의 장점은 다음과 같다.

  • 동일한 인터페이스 적용:
    • 개별 객체와 그룹을 동일한 인터페이스 (Component 또는 Animal 등)를 통해 처리한다. 
    • 이로 인해 클라이언트는 객체와 그룹을 구분하지 않고 동일한 방식으로 다룰 수 있다.
    • 이로 인해 코드의 일관성과 유연성이 증가한다.
  • 자동화된 함수 호출:
    • 복잡한 트리 구조가 있을 때, 루트에서 함수를 한 번만 실행해도 해당 함수는 리프 노드까지 자동으로 호출된다. 
    • 이로 인해 복잡한 계층적 구조를 간편하게 다룰 수 있게 된다.
    • 특히 객체와 그 구성요소 사이에서 일관된 동작을 보장하게 해 준다.
  • 확장성:
    • 새로운 구성요소의 추가가 간편하다. 새로운 Leaf나 Composite를 도입하는 것만으로 확장이 가능하다.
    • 예를 들어, "Bird"와 같은 새로운 동물 종류를 추가하려면 Animal 인터페이스를 상속받아 구현하면 된다.
  • 단순화:
    • 클라이언트는 개별 객체와 그룹 객체를 구분하지 않아도 된다. 
    • 이로 인해 코드의 복잡도가 감소하며, 구성요소의 내부 구조를 클라이언트 코드 변경 없이 유연하게 수정할 수 있다.
  • 재사용성:
    • 동일한 인터페이스에 따라 설계된 객체나 그룹은 쉽게 재사용이 가능하다. 
    • 예를 들어, 한 동물원에서 다른 동물원으로 동물 그룹을 옮기려면 해당 그룹 객체를 가져와서 다른 그룹에 추가하는 것만으로도 충분하다.
  • 재귀적인 구조:
    • Composite 클래스를 통해 복합 객체 안에 또 다른 복합 객체를 추가하는 것이 가능해진다.
    • 이는 트리 구조의 복잡한 계층을 쉽게 표현할 수 있게 해 준다.
    • 이를 통해 예를 들면 동물원 내의 구역, 부구역 등을 쉽게 표현할 수 있게 된다.

결론

컴포지트 패턴은 객체와 그룹의 복잡성을 효과적으로 관리할 수 있는 구조를 제공한다. 그룹과 개별 항목을 동일한 인터페이스로 취급함으로써 코드의 일관성과 유연성을 높이고, 복잡한 트리 구조나 계층 구조를 간결하게 표현할 수 있다.
더불어, 이 패턴을 사용하면 특정 객체나 그룹의 동작을 일관적으로 적용할 수 있으며, 이는 코드의 재사용성과 확장성에 큰 이점을 제공한다. 즉, 새로운 구성요소나 기능을 추가하거나 기존의 구성요소를 수정하고자 할 때, 상위 수준에서의 큰 변화 없이도 이를 수행할 수 있다.
또한, 클라이언트는 구성요소의 실제 구현이나 내부 구조에 대해 알 필요가 없으므로, 코드를 더 간결하고 관리하기 쉽게 만든다.
결국, 컴포지트 패턴은 객체 지향 설계의 원칙 중 하나인 "객체를 확장에는 열려 있게, 변경에는 닫혀 있게 만들어야 한다"는 원칙을 잘 지키는 패턴으로 볼 수 있다. 

 


출처

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

https://youtu.be/XXvrHAsfTso

https://dawonny.tistory.com/m/218