Composite

DongHeon·2022년 11월 16일
0

디자인 패턴

목록 보기
7/12

오늘은 Composite 패턴에 대해 알아보겠습니다.

Composite?

Composite 패턴은 전체 계층 구조에서 부분적인 객체를 동일하게 취급할 수 있도록 하는 패턴입니다.

해당 객체를 사용하는 Client 입장에서는 '전체'나 '부분'이나 동일한 인터페이스로 인식할 수 있는 계층 구조를 만드는 것입니다.

코드

패턴 적용 전

class Item {
    private var name: String
    private var price: Int
    
    init(name: String, price: Int) {
        self.name = name
        self.price = price
    }
}

class ShoppingCart {
    private var items = [Item]()
    
    func addItem(item: Item) {
        items.append(item)
    }
    
    func getItems() -> [Item] {
        return self.items
    }
}

ItemShoppingCart 두 개의 타입이 있습니다.

Client 쪽에서 해당 타입을 생성해 가격을 확인하는 코드를 작성해 보겠습니다.

class Customer {
    let myCart = ShoppingCart()
    
    func buyItem(item: Item) {
        myCart.addItem(item: item)
    }
    
    func calculateItemPrice(item: Item) -> Int {
        return item.getPrice()
    }
    
    func calculateAllItemPrice() -> Int {
        let priceArray = myCart.getItems().map { Item in
            Item.getPrice()
        }
        
        return priceArray.reduce(0) { $0 + $1 }
    }
}

let customer = Customer()
let item = Item(name: "라면", price: 1000)
let cookie = Item(name: "과자", price: 1500)
let juice = Item(name: "음류수", price: 800)

customer.buyItem(item: item)
customer.buyItem(item: cookie)
customer.buyItem(item: juice)

customer.calculateItemPrice(item: item)
customer.calculateAllItemPrice()

위 코드에서 보다 싶이 ShoppingCart에 담긴 물건에 가격을 얻거나 물건 하나에 대한 가격을 알고 싶다면 따로 메서드를 생성해 주어야 합니다.

또 한 CustomerShoppingCart에 대한 정보만 알고 있고ShoppingCart에 정확히 어떤 Item들이 담겨있는지 알 수 없습니다. 즉, Item 들에 대한 정보를 알고 있는 ShoppingCart에서 전체 가격을 리턴하는 메서드를 가지고 있어야 할 것 같습니다.

(이전에 책에서 본 내용 중에 책임 할당의 기본 원칙을 여기서 지켜야 하는 게 아닐까라는 생각을 합니다.)

책임 할당의 기본 원칙 : 책임을 수행하는 데 필요한 정보를 가진 객체에서 그 책임을 할당하는 것이다.

패턴 적용

  • Component
protocol Component {
    func getPrice() -> Int
}

Component에는 Client에서 사용할 공통된 기능을 정의합니다.

  • Leaf
class Item: Component {
    private var name: String
    private var price: Int
    
    init(name: String, price: Int) {
        self.name = name
        self.price = price
    }
    
    func getPrice() -> Int {
        return self.price
    }
}
  • Composite
class ShoppingCart: Component {
    private var items = [Item]()
    
    func addItem(item: Item) {
        items.append(item)
    }
    
    func getItems() -> [Item] {
        return self.items
    }
    
    func getPrice() -> Int {
      	let priceArray = items.map { Item in
            Item.getPrice()
        }
        
        return priceArray.reduce(0) { $0 + $1 }
    }
}

Leaf는 이제 부분에 해당하고 CompositeLeaf를 가지고 있는 전체에 해당합니다.

class Customer {
    let myCart: ShoppingCart
    
    init(myCart: ShoppingCart) {
    	self.myCart = myCart
    }
    
    func buyItem(item: Item) {
        myCart.addItem(item: item)
    }
    
    func calculatePrice(item: Component) -> Int {
        return item.getPrice()
    }
}

Client 코드에서는 이제 Item에 대한 가격이나 ShoppingCart에 저장된 Item 가격의 총합을 하나의 메서드를 통해 알 수 있습니다.

장단점

  • 장점
  1. 복잡한 트리 구조를 편리하게 사용할 수 있다.
  2. 다형성과 재귀를 활용할 수 있다.
  3. Client 코드를 변경하지 않고 새로운 요소 타입을 추가할 수 있다.
  • 단점
  1. 트리를 만들어야 하기 때문에 지나치게 일반화 해야 하는 경우도 발생할 수 있다.
    (제가 생각했을 때 예시에서 과연 모든 객체에서 가격을 반환하는 메서드가 필요한지 고민할 필요가 있어 보인다. 예를 들어 서비스로 주는 물건이 있다면 해당 물건에는 가격이 없다. 그렇다면 해당 물품의 가격은 0으로 반환해야 하는 걸까 아니면 해당 메서드 자체를 구현할 필요가 없을까 고민해 보면 좋을 것 같습니다.)

해당 글은 인프런의 코딩으로 학습하는 GoF 디자인 패턴 강의를 참고해 작성했습니다.

⭐️ 부족하거나 잘못된 부분이 있다면 댓글은 언제나 환영입니다!! ⭐️

0개의 댓글