디자인 패턴(구)/구조 패턴

복합체(Composite) 패턴이란?

공대키메라 2022. 4. 12. 23:04

1. 복합체(Composite) 패턴이란?

의도

부분과 전체의 계층을 표현하기 위해 객체들을 모아 트리 구조로 구성한다. 사용자로 하여금 개별 객체와 복합 객체를 모두 동일하게 다룰 수 있도록 하는 패턴.

 

그룹 전체와 개별 객체를 동일하게 처리할 수 있다. 

 

클라이언트 입장에서는 전체나 부분이나 모두 동일한 컴포넌트로 인식할 수 있는 계층구조를 만든다.

사용 시기

  • 부분 - 전체의 객체 계통을 표현하고 싶을 때
  • 사용자가 객체의 합성으로 생긴 복합 객체와 개개의 객체 사이의 차이를 알지 않고 자기 일을 할 수 있도록 만들고 싶을 때, 사용자는 복합 구조의 모든 객체를 똑같이 취급

구조

  • Component : 집합 관계에 정의될 모든 객체에 대한 인터페이스를 정의
  • Leaf : 가장 말단의 객체. 즉, 자식이 없는 객체. 나뭇가지는 서로 여러개의 나뭇가지가 엮에 있지만 결국 끝에는 나뭇잎이 있다. 이것을 비유해서 아무래도 복합체를 하나의 나무에 비유한 것 같다. 
  • Composite : 자식이 있는 구성요소에 대한 행동을 정의. 자신이 복합하는 요소를 저장 및 COmponent 인터페이스에 정의된 자식 관련 연산을 구함 
  • Client : Component 인터페이스를 통해 복합 구조 내의 객체들을 조작

 

컴포짓 패턴을 구현하는데 중요한 점은 composite는 leaf타입을 참조하면 안된다는 것이다. 

 

내가 말하는건 맞는건 아니고 그냥 느낌으로는 무언가... 꼬리에 꼬리에 꼬리를 무는 것이 Tree구조 처럼 쭉 뻗어나 있는 것 같다는 느낌이다.

 

2. 구현 - before

필자는 마트에서 어머니께서 보신 장바구니를 들고 있다. 

장바구니에는 물품이 쌓인다.

하나.... 둘...

그런데 한번씩 나에게 어떤 물건을 샀는지 묻는다. 

장바구니에 있는 세부 정보를 말할 때 마다 나는 짜증이난다.

아니... 내가 이걸 전부 다 알아야 하는거야?

그리고 무슨 봉지 안에 다른 봉지가 또 있는지... 어휴 너무 짜증난다... 

이를 작업하기 위해 다음과 같이 패키지를 전과 후로 나누어서 작업했다.

Bag.java

import java.util.ArrayList;
import java.util.List;

public class Bag {

    List<Grocery> groceryList = new ArrayList<>();

    public void add(Grocery grocery) {
        groceryList.add(grocery);
    }

    public List<Grocery> getItems() {
        return groceryList;
    }
}

Grocery.java

public class Grocery {

    private String name;

    private int price;

    private String type;

    public Grocery(String name, int price, String type) {
        this.name = name;
        this.price = price;
        this.type = type;
    }

    public int getPrice() {
        return this.price;
    }

}

Customer.java

public class Customer {

    public static void main(String[] args) {
        Grocery mushroom = new Grocery("송이버섯", 1000, "버섯과");
        Grocery seaweed = new Grocery("미역", 2000, "해조류과");

        Bag bag = new Bag();
        bag.add(mushroom);
        bag.add(seaweed);

        Customer customer = new Customer();
        customer.printPrice(mushroom);
        customer.printPrice(bag);
    }

    private void printPrice(Grocery mushroom) {
        System.out.println(mushroom.getPrice());
    }

    private void printPrice(Bag bag) {
        int total = bag.getItems().stream().mapToInt(Grocery::getPrice).sum();
        System.out.println("total = " + total);
    }

}

 

현재 구조에서는 고객인 내가 물품에 대해 너무 많은 것을 알아야 한다. 무엇을 샀는지 직접 찾아서 일일이 보는것이 얼마나 화나는 일인가!

 

여기에 우리가 Composite 디자인 패턴을 적용하면 좀 더 쉽게 문제를 해결할 수 있다.

 

나의 입장에서는 구입한 물품에 대해 위처럼 일일이 메소드를 만들어서 찾지 않고, 전체나 부분이나 모두 동일한 컴포넌트로 인식할 수 있는 계층구조를 만들어 해결하고 싶다.

 

즉, 어떤 물건이 있는지, 가격이 무엇인지 확인하는 작업을 동일한 컴포넌트에서 처리하고 싶다. 

 

그러면 다음과 같이 하면 된다. 

3. 구현 - after

생각해보면 어머니께서 나에게 시킨 일은 어떤 물건인지 보고, 얼마인지 보는게 전부다

 

어떤 것이 있는지 나는 정확히 생각하지 않고, 그냥 넘겨주기만 하였다.

Bag.java - modified

import java.util.ArrayList;
import java.util.List;

public class Bag implements Component{

    List<Component> components = new ArrayList<>();

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

    public List<Component> getComponents() {
        return components;
    }

    @Override
    public int getPrice() {
        return components.stream().mapToInt(Component::getPrice).sum();
    }

    @Override
    public String getName() {
        StringBuilder builder = new StringBuilder();
        for (Component component : components) {
            builder.append(component.getName() + " / ");
        }
        return builder.toString();
    }
}

Grocery.java - modified

public class Grocery implements Component{

    private String name;

    private int price;

    private String type;

    public Grocery(String name, int price, String type) {
        this.name = name;
        this.price = price;
        this.type = type;
    }

    @Override
    public int getPrice() {
        return this.price;
    }

    @Override
    public String getName() {
        return this.name;
    }

}

Component.java

public interface Component {
    int getPrice();
    String getName();
}

Customer.java

public class Customer {

    public static void main(String[] args) {
        Grocery mushroom = new Grocery("송이버섯", 1000, "버섯과");
        Grocery seaweed = new Grocery("미역", 2000, "해조류과");

        Bag bag = new Bag();
        bag.add(mushroom);
        bag.add(seaweed);

        Customer customer = new Customer();
        customer.printPrice(mushroom);
        customer.printName(mushroom);
        customer.printPrice(bag);
    }

    private void printName(Component component) {
        System.out.println(component.getName());
    }

    private void printPrice(Component component){
        System.out.println(component.getPrice());
    }


}

 

어떤 물건을 샀는지 나(Customer)는 구체적으로 알 필요가 없다. 

 

그 가격을 어떻게 알고, 이름을 어떻게 처리하는지는 composite 객체, 혹은 Leaf에 해당하는 객체가 알고 있다.

 

어떤 물건을 담았던, 결국 물건을 담았다는 것에 우리는 집중할 필요가 있다.

 

그 물건은 우리의 기준에 맞게 가방에 잘 담아서 넣으면 그대로 꺼내면 그만이다.

 

즉, 클라이언트에서는 단지 위임만 하면 된다. (가방에 싸서 넣으면 된다.)

 

전체나 부분이나 클라이언트 부분에서는 동일하다!


나무에 빗대서 컴포짓 패턴을 바라본다면,

 

한줄기의 나무가 있는데 이것을 기점으로 여러 갈래가 뻣어 나간다. 

 

이것을 잘르고, 다른 갈래로 뻣어 나간다고 해도 우리는 다른 나무라고 부르지 않는다. 

 

나뭇가지도 결국은 나무의 한 부분이고, 같은 나무이다. 

 

위의 설명이 부족했다면 다음 영상을 참고하면 더욱 이해가 잘 될 것이다. 

https://www.youtube.com/watch?v=EWDmWbJ4wRA&t=17s