티스토리 뷰

1. 컴포지트 패턴이란

컴포지트 패턴을 이용하면 객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들 수 있다.

이 패턴을 이용하면 클라이언트에서 개별 객체와 다른 객체들로 구성된 복합 객체를 똑같은 방법으로 다룰 수 있다.

 

구성요소

Composite라는 단어 자체가 복합적인, 합성적인이라는 뜻이다.
어떤 객체 구조가 여러 계층으로 구성되어 있는 복잡한 구조일 때 적용하는 패턴인데, 전형적인 컴포지트 객체는 이런 식으로 생겼다.

Component

- 복합적인 구조를 지닌 객체 (Composite object) 들의 공통 인터페이스

- 모든 객체들이 가지고 있는 default한 기능을 여기서 구현할 수 있다.

- 자녀 컴포넌트에 접근하거나 관리하는 인터페이스를 선언한다.

- (선택사항) 필요하다면 자녀에서 부모 컴포넌트로 재귀적인 접근을 하는 기능도 제공할 수 있다.

 

Leaf

- leaf 객체는 자녀를 가지지 않는다.

- 복합구조 내에서 가장 기본적인 (primitive) 기능등을 제공한다.

 

Composite

- 자녀를 가지는 컴포넌트들이 가질 기능들을 여기서 선언한다.

- 자녀 컴포넌트를 가진다.

- 자녀 컴포넌트들과 연관된 함수들을 이 인터페이스에서 구현한다.

 

Client

- Component 인터페이스를 통해 복합구조 내의 객체들을 조작한다.

 

 

2. 동기

 

장점

- 복잡한 트리 구조를 편리하게 사용할 수 있다.

- 다형성과 재귀를 활용할 수 있다.

- 클라이언트 코드를 변경하지 않고 새로운 아이템 타입을 추가할 수 있다. (Open-closed principle)

 

단점

- 트리를 만들어야하기 때문에 (공통된 인터페이스를 정의 해야하기 때문에) 지나치게 일반화 해야 하는 경우도 생길 수 있다.

 

3. 구현

구현 예시

public class Item {

    private String name;

    private int price;

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

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

public class Bag {

    private List<Item> items = new ArrayList<>();

    public void add(Item item) {
        items.add(item);
    }

    public List<Item> getItems() {
        return items;
    }
}

public class Client {

    public static void main(String[] args) {
        Item doranBlade = new Item("도란검", 450);
        Item healPotion = new Item("체력 물약", 50);

        Bag bag = new Bag();
        bag.add(doranBlade);
        bag.add(healPotion);

        Client client = new Client();
        client.printPrice(doranBlade);
        client.printPrice(bag);
    }

    private void printPrice(Item item) {
        System.out.println(item.getPrice());
    }

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

위 예시에서는 Item과 Bag가 복합적인 구조를 가진다.

이 구조를 Composite Pattern을 적용하여 Item과 Bag가 Component라는 인터페이스를 공통으로 가지게 만들어 보자.

public interface Component {
    int getPrice();
}

public class Item implements Component {

    private String name;

    private int price;

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

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

public class Bag implements Component {

    private 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();
    }
}

이렇게 Component의 getPrice()를 오버라이딩하게 되면 Client 코드는 다음과 같이 개선할 수 있다.

public class Client {

    public static void main(String[] args) {
        Item doranBlade = new Item("도란검", 450);
        Item healPotion = new Item("체력 물약", 50);

        Bag bag = new Bag();
        bag.add(doranBlade);
        bag.add(healPotion);

        Client client = new Client();
        client.printPrice(doranBlade);
        client.printPrice(bag);
    }

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

추가로 만약에 새로운 타입의 객체를 넣는다고 하더라도 클라이언트 코드는 변경없이 그저 Component를 구현하게 하면 된다.

public class Character implements Component {

    private Bag bag;

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