구조패턴 - 컴포짓
이전 글 : 구조패턴 - 브릿지
2024.03.28 - [Engineering/SW Design] - [디자인 패턴] 구조패턴 - 브릿지
[디자인 패턴] 구조패턴 - 브릿지
구조패턴 - 브릿지 이전 글 : 구조패턴 - 어댑터 2024.03.25 - [Engineering/SW Design] - [디자인 패턴] 구조패턴 - 어댑터 [디자인 패턴] 구조패턴 - 어댑터 구조패턴 - 어댑터 이전 글 : 구조패턴 - 싱글턴 202
skidrow6122.tistory.com
요약
클라이언트가 복합 객체 (group of object) 나 단일 객체 에 연연하지 않고 동일하게 취급하는 것을 목적으로 한다.
트리 관계의 객체를 처리 하기 위함이 목적이며, Data modeling 에서의 BOM (bill of material) 개념과 흡사하다고 보면 된다.
궁극적으로 composite 의 의도는 트리구조로 객체를 작성하여, 전체-부분 관계를 표현하는 것이다.
트리하면 무조건 나오는 개념인 리프 노트와 브랜치가 적용되며, 여기서 파생되는 복잡성에 따른 에러를 해결하기 위해 인터페이스를 활용한다.
대표적인 예시로서 Directory - File 관계에서 전체-부분 관계를 효율적으로 정의 할 수 있다.
패턴은 컴포넌트 (인터페이스) + 리프 구현 + 컴포짓 구현 총 3가지 레이어로 나뉜다.
- component
- 클라이언트가 composition 내의 오브젝트를 다루기 위해 제공되는 인터페이스
- interface 또는 abstract 클래스로 정의되며 모든 오브젝트들에게 공통되는 메소드를 정의 해야 함
- Leaf
- composition 내 오브젝트들의 행동을 정의
- 다른 컴포넌트들에 대해 참조를 가지면 안됨
- Composite
- Leaf 객체들로 이루어져 있고 컴포넌트 내 명령들을 구현
키워드 : 트리 / 컴포넌트(인터페이스) / 리프 / 컴포짓 / 재귀 객체 구조 / 상품과 상품 옵션
문제
제품들과 상자들이라는 두가지 유형의 객체들이 있다고 가정한다.
상자에는 여러개의 제품들과 여러개의 더 작은 상자들이 포함될 수 있다.
이 작은 상자들은 또 일부 제품들을 또는 더 작은 상자들 등을 담을 수 있다.
이러한 상황에서 각 제품은 클래스라고 치고, 이러한 클래스들을 사용하는 주문 시스템이 있다고 가정해보자.
주문들에는 포장이 없는 단순한 제품들과, 제품들로 채워진 상자들 또는 더 작은 단위의 상자들이 포함 될 수 있다.
이때 주문의 총 가격을 어떻게 계산 할 수 있을까?
별 생각 없이 이문제에 접근하자면, 모든 상자를 일단 다 풀어 헤친 후에 내부 제품 가격의 합을 구하는 것을 고려해볼 수 있지만,,,
이 방법은 당연히 루프를 굉장히 많이 돌려야 하므로 효율적이지 않다.
해결책
컴포짓 패턴에서는 총 가격을 계산하는 메서드를 선언하는 공통 인터페이스를 통해서 제품들과 상자들 클래스를 풀어나갈 수 있도록 해준다.
- 제품들의 경우, 이 메서드은 단순히 제품 가격을 반환한다.
- 상자들의 경우, 이 메서드는 상자에 포함된 각 제품의 항목을 살펴보고 가격을 확인 한뒤, 해당 상자의 총 가격을 반환한다.
- 만약 이 항목들 중 하나가 더 작은 상자라면, 메서드는 해당 상자의 모든 내부 구성품의 가격이 계산될 때 까지 내용물을 살펴본다.
- 여기에 최종가격에 만약 포장 비용같은것이 붙는다 해도 특수 로직으로 넣을 수 있다.
이 접근 방식은 더 이상 트리를 구성하는 객체들의 구상 클래스들에 대해 신경 쓸 필요도, 또 물건이 단순한 제품인지 내용물이 있는 상자인지 알 필요도 없다는 점이다.
단순히 공통 인터페이스를 통해 모두 같은 방식으로 처리하면 된다.
이 메서드를 호출만하면 객체들 자체가 요청을 트리 아래로 전달하기 때문이다.
구조
- Component 에 해당하는 interface 에서 공통의 함수 (아까 예시에서의 가격 계산 함수 등) 을 제공
- Leaf 는 더이상 하위 상자가 없는 ‘제품들’ 에 해당
- Composite 는 그 자체가 하위 상자로 구성된 ‘상자들’ 에 해당
Hands on
Shape 이라는 모양 인터페이스인 component interface 가 존재하고, 여기에 컬러를 받아서 draw 하는 함수가 있다고 가정하자.
이 인터페이스는 leaf 로서 삼각형, 사각형, 원형을 각각 구현 할 수 있다.
public interface Shape {
fun draw(paintColor: String)
}
- Component 에서는 leaf 와 Composite의 공통이 되는 메소드가 정의 된다.
- 따라서, Shape 인터페이스 내 각각 도형에 색깔을 칠하는 draw() 메소드를 정의 하였다.
public class Triangle : Shape {
override fun draw(paintColor: String) = println("삼각형 색깔" + paintColor)
}
public class Square : Shape {
override fun draw(paintColor: String) = println("사각형 색깔" + paintColor)
}
public class Circle : Shape {
override fun draw(paintColor: String) = println("원형 색깔" + paintColor)
}
- Leaf 객체들은 아래에 구현할 Composite에 포함되는 요소이다.
public class CompositeDrawing(val shapes: ArrayList<Shape> = ArrayList()) : Shape {
override fun draw(paintColor: String) {
for (shape in shapes) {
shape.draw(paintColor)
}
}
fun add(s: Shape) = this.shapes.add(s)
fun remove(s: Shape) = this.shapes.remove(s)
fun clear(s: Shape) {
println("모든 도형 삭제")
this.shapes.clear()
}
}
- Composite객체는 Leaf 객체를 포함하고 있고, Component 인 Shape를 구현한다.
- 또한, Leaf 그룹에 대해서 add, remove, clear 할 수 있는 메소드를 클라이언트에 제공 한다.
- 여기서 중요한 점은 Composite의 객체 또한 Component를 구현해야 한다는 것이다. (draw())
- 이렇게 해야만 클라이언트가 Composite 객체에 대해 다른 leaf 와 동일하게 취급할 수 있다.
fun main(){
// 삼각형 leaf들을 초기화
val triangle1 = Triangle()
val triangle2 = Triangle()
val triangle3 = Triangle()
// 원형 leaf들을 초기화
val circle1 = Circle()
val circle2 = Circle()
val circle3 = Circle()
// component 를 초기화 (상자)
val shape = CompositeDrawing()
val shape1 = CompositeDrawing()
val shape2 = CompositeDrawing()
// 첫번째 상자에 삼각형 두개, 원형 두개를 담고 파란색으로 색을 칠한다.
shape1.add(triangle1)
shape1.add(triangle2)
shape1.add(circle1)
shape1.add(circle2)
shape1.draw("blue")
// 두번째 상자에 삼각형 하나, 원형 하나를 담고 빨간색으로 색을 칠한다.
shape2.add(triangle3)
shape2.add(circle3)
shape2.draw("red")
//상자두개를 큰 상자에 담고 모두 검정색으로 색을 칠한다.
shape.add(shape1)
shape.add(shape2)
shape.draw("black")
}
- CompositeDrawing 객체를 통해 Triangle 과 Circle 이라는 Leaf 객체들을 그룹으로 묶어서 한번에 동작 시켰다.
- 또한, 사용되지는 않았지만 Square같은 다른 도형도 마찬가지로 Shape 인터페이스를 구현하고 있기 때문에 함께 취급 될 수 있다.
핵심
- 트리 구조의 객체구조를 구현해야 할때 사용
- 공통 인터페이스를 공유하는 두가지 기본 요소 유형인 leaf 와 composite 컨테이너를 제공한다.
- 이를 통해 트리 구조와 유사하게 중첩된 재귀 객체 구조를 구성할 수 있다.
- 클라이언트 코드가 단순 leaf 요소들과 composite 된 요소들을 모두 균일하게 처리하고 싶을때 사용
- 기능이 너무 다른 클래스들에는 공통 인터페이스를 제공하기 어려우므로, 굳이 과도하게 일반화해서 이해하기 어렵게 만들 필요는 없음
간단히 hands on 을 통해 컴포짓 패턴을 살펴 보았다.
'Engineering > SW Design' 카테고리의 다른 글
[디자인 패턴] 구조패턴 - 브릿지 (0) | 2024.03.28 |
---|---|
[디자인 패턴] 구조패턴 - 어댑터 (0) | 2024.03.25 |
[디자인 패턴] 생성패턴 - 싱글턴 (0) | 2024.03.11 |
[디자인 패턴] 생성패턴 - 프로토타입 (0) | 2024.03.03 |
[디자인 패턴] 생성패턴 - 빌더 (0) | 2023.05.25 |
댓글