Java/Design Pattern

[디자인 패턴] 구조 패턴 - 컴포지트 패턴 (Composite Patterns)

민돌v 2023. 3. 1. 23:43
728x90
(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다

 

#1. 객체 생성 관련 패턴

#2. 구조 관련 패턴

 

#3. 행동 관련 패턴

 

 


✔️ 컴포짓 패턴

클라이언트가, 그룹 전체와 개별 객체를 동일하게 처라할 수 있도록 하는 패턴
  • 전체 계층구조와 전체를 구성하는 부분 계층구조를 클라이언트 입장에서 동일하게 바라볼 수 있도록(하나의 인터페이스로) 구성하는 패턴 입니다.
  • 클라이언트 입장에서는 전체부분이나 모두 동일한 컴포넌트로 인식할 수는 계층 구조 를 만든다. (Part-Whole Hierarchy)
  • 컴포짓 패턴은, 클라이언트 입장에서 전체 계층이든, 부분 계층이든, 최하위 계층이든 동일하게 바라보도록 만듭니다.
  • 컴포짓 패턴은 트리 구조에 종속적입니다.

 

컴포짓 패턴

 

👏🏻 컴포짓에서는 클라이언트가 언제나 Leaf 타입을 참조하는게 아니라 Composite 타입을 참조해야합니다.

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

 

✔️ 컴포짓 패턴 구현하기

예시를 들기위해, 각각의 가격을 가지고 있는 Item 클래스와 Item을 담을 수 있는 Bag 클래스를 정의합니다.

@Getter
public class Item {

    private String name;
    private int price;

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

@Getter
public class Bag {

    private List<Item> items;

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

클라이언트에서 아이템의 가격 정보와, Bag 클래스가 담고있는 아이템 가격의 총 집합을 계산하는 로직을 호출합니다.

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

    //클라이언트가 너무 많은 정보를 가지고 있음 -> OOP 위반
    private void printPrice(Item item) {
        System.out.println(item.getPrice());
    }

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

→ 해당 로직은 매우 단순하게 가격정보를 호출할 수 있지만, 클라이언트에서 모든 정보를 가져가기 때문에 객체지향적으로 봐도 옳지않고, Item 외에도 skin 이나 pood 등 새로운 객체가 생성되면 같은 역할을 하는 메소드를 파라미터만 다르게 계속 만들어야합니다.

이 클라이언트가 바라보는 파라미터를 하나의 최상위 인터페이스(Component)로 추상화를 하면 client 는 하위 로직은 전혀 몰라도 되고,

그저 Component 의 가격을 물어보기만 하면 됩니다.

⬇️

컴포짓 패턴 적용

1. 먼저 클라이언트가 바라볼 최상위 인터페이스 Component 클래스를 정의합니다.

public interface Component {

    int getPrice();
}

2. Leaf 클래스, 즉 Bag 과 Item 클래스는 Component 클래스를 구현합니다.

  • Bag(Leaf 클래스) 에서 Component  List 를 가지고 있는 이유는 트리구조를 만들기 위해서 입니다. 👏🏻
@Getter
public class Bag implements Component{

    private List<Component> components = new LinkedList<>();

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

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

@Getter
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;
    }
}

 

3. 이제 클라이언트는, 무엇을 호출하든 상관없이 Component 만 바라봅니다.

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

}

 

활용 상황

  • 객체들 간에 계급 및 계층구조가 있고 이를 표현해야할때 유용합니다.
  • 클라이언트가 단일 객체와 집합 객체를 구분하지 않고 동일한 형태로 사용하고자 할때 유용합니다.

 


 

✔️ 컴포짓 패턴 장점,  단점

• 장점

  • 복잡한 트리 구조를 편리하게 사용할 수 있다.
  • 최상위 클래스를 바로보고 있기 때문에 다형성과 재귀를 활용할 수 있습니다.
    • 📌(하지만 트리구조를 이루기때문에 하위 구조를 생성자-소비자 패턴으로 사용하면 오히려 재귀를 피할수 있다는 장점도 가져갈 수 있을 것 같습니다 ..!?) 
  • 클라이언트 코드를 변경하지 않고 새로운 엘리먼트 타입을 추가할 수 있습니다. 객체들이 모두 같은 타입으로 취급되기 때문에 새로운 클래스 추가가 용이합니다.
  • 단일 객체 및 집합 객체를 구분하지 않고 코드 작성이 가능하여 사용자 코드가 단순해집니다.

 

 단점

  • 트리를 만들어야 하기 때문에 (공통된 인터페이스를 정의해야 하기 때문에) 지나치게 일반화 해야 하는 경우도 생길 수 있습니다.
  • 즉, 설계가 지나치게 많은 범용성을 가집니다.
  • 복합체의 구성 요소에 제약을 가하기 힘듭니다. 복합체가 오직 한개의 구성 요소만 있었으면 할때가 있는데 Composite 클래스만 가지고는 이를 제어하기 어렵기에 런타임 점검이 들어가게 됩니다.

 

 

컴포지트 패턴 끝..!

 

 

 

반응형