개발자로 후회없는 삶 살기
디자인 패턴 PART.컴포짓 패턴 본문
서론
※ 이 포스트는 다음 강의의 학습이 목표임을 밝힙니다.
https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/dashboard
본론
- 컴포짓 패턴 소개
전체 계층 구조와 그 계층 구조를 구성하는 부분적인 객체를 클라이언트 입장에서 동일하게 취급할 수 있게끔 하는 것입니다. 클라이언트 입장에서는 이게 전체인지 부분인지 아무 상관없이 동일한 인터페이스로 사용할 수 있습니다. 따라서 특정한 트리 구조를 구성해야 합니다.
- 코드
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);
}
클라이언트 코드에 아이템과 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);
}
클라이언트는 아이템의 가격과 가방에 있는 모든 아이템의 가격을 출력합니다. 근데 이렇게 되면 클라이언트가 너무 많은 것을 알아야합니다. 가방의 아이템의 모든 가격을 구하는 로직이 클라이언트에 있는게 맞는 것인지 의문이 드는 코드입니다.
=> 구조
컴포짓을 적용하면 이 문제를 해결할 수 있습니다. 클라이언트는 값을 구해야하는 모든 컴포넌트들에 공통적인 인터페이스를 정의하고 컴포넌트 인터페이스 타입만 바라봅니다.
우리가 만들 아이템, 백 들은 Leaf(가장 기본 적인 단위), Composite(Leaf의 그룹)이 있고 컴포짓은 여러개의 컴포넌트 배열을 가지고 있습니다. 근데 이때도 타입이 Leaf가 아니고 컴포넌트 타입으로 표현합니다.
- 패턴 적용
1. 컴포넌트
public interface Component {
int getPrice();
}
컴포넌트라는 공통 인터페이스를 정의해야 합니다. 여기서는 클라이언트가 아이템과 백에 동일하게 사용하던 가격을 구하는 oper를 넣어야 합니다. 공통된 oper를 이 인터페이스에 둬야하는 것이 중요한 것입니다.
따라서 클라이언트가 공통으로 사용할 oper인 getPrice를 정의했습니다.
2. Item
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;
}
}
컴포넌트 인터페이스를 구현합니다. 그리고 getPrice를 재정의 해야합니다.
3. Bag
public class Bag implements Component{
private List<Component> components = new ArrayList<>();
public void add(Component component) {
components.add(component);
}
public List<Component> getItems() {
return components;
}
@Override
public int getPrice() {
return components.stream()
.mapToInt(Component::getPrice)
.sum();
}
}
역시 컴포넌트를 구현해야 하고 중요한 것은 타입을 바꿔줘야 합니다. 컴포짓에서는 절대로 Leaf 타입을 참조하면 안 된다고 했습니다. 지금 이 백과 아이템이 곧 컴포짓입니다. 따라서 컴포넌트를 참조해야 합니다.
이렇게 하면 어떤 컴포짓에 들어있는 컴포넌트의 가격을 구하는 로직이 컴포짓에 들어옵니다. 클라이언트가 지나치게 많은 것을 알지 않아도 되고 클라이언트는 Bag에 메세지를 보내서 가격을 구하라고 호출할 수 있습니다.
4. 클라이언트
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());
}
이전엔 Item가 Bag이 따로 있었는데 이제는 컴포넌트 타입 하나만 가지고 getPrice를 합니다. 이렇게 기존 코드를 컴포짓 패턴을 적용해서 개선했고 그 결과 클라이언트는 가격을 어떻게 구할 지 몰라도 되고 가격을 어떻게 구할지는 컴포짓 객체가 알아냅니다.
- 장, 단점
복잡한 트리 구조를 편리하게 사용할 수 있다는 장점이 있습니다. 컴포넌트라는 공통된 인터페이스를 구현하고 있다면 다형성을 활용하기도 좋습니다.
private void printPrice(Component component) {
System.out.println(component.getPrice());
}
그리고 클라이언트는 새로운 타입의 Leaf가(컴포짓) 추가되더라도 클라이언트의 코드는 바뀌지 않습니다. 특히 printPrice의 코드는 절대로 변하지 않습니다. 하지만 트리를 만들어야 해서 지나치게 일반화해야 하는 경우가 생길 수도 있습니다. 그럴 때는 지금 상황에 컴포짓 패턴이 좋은게 맞는지 의심을 해봐야 합니다.
'[백엔드] > [Java | 학습기록]' 카테고리의 다른 글
디자인 패턴 PART.퍼사드 패턴 (0) | 2023.08.21 |
---|---|
디자인 패턴 PART.데코레이터 패턴 (0) | 2023.08.20 |
디자인 패턴 PART.브릿지 패턴 (0) | 2023.08.18 |
디자인 패턴 PART.어댑터 패턴 (0) | 2023.08.18 |
디자인 패턴 PART.프로토타입 패턴 (0) | 2023.08.17 |