본문 바로가기

IT/헤드퍼스트 디자인패턴

[디자인패턴] 데코레이터 패턴


안녕하세요 남갯입니다.


오늘은 데코레이터 패턴에 대해 포스팅 해보려고합니다.


사건의 시작


커피숍 커피점에서는 


Beverage 의 추상클래스를 만들어 음료를 제공하고 있었는데,




커피에는 두유, 우유, 모카 등을 추가하거나 휘핑크림도 추가가 가능하게 해야합니다.


그러다보니 여러개의 클래스를 만들었습니다.



Beverage()

class DartRoastWithWhip()

class DartRoastWithSoy()

class HouseBlendWithSoy()

class HouseBlendWithSoy()

.....


이렇게 각각을 생성해서 Cost()를 구하게되면 내용물의 가격이 바뀐다던가 커피의 가격이 바뀔때마다 Cost를 수정해줘야 합니다.


그래서 상속을 통해 관리하면 안되나요?

그럼 상속을 통해  상위클래스를 만들어 보겠습니다.




.....모든 파라미터의 hasMilk(), setMilk(), hasSoy(), setSoy() 등등 첨가물을 받아 만들었지만 


코드를 직접 치게 된다면 

hasMilik(){

cost += 10.0

}

....... 이런식으로 계속 써야합니다.


문제점


1. 첨가물의 가격이 바뀌면 기존코드를 수정해야하고

2. 종류가 추가되거나 삭제되면 수정해야하고

3. 음료가 추가되면 휘핑크림을 안올리는 음료에도 hasWhip을 추가하게 될것이고

4. 더블모카를 주문하게되면?


위의 UML에서는 4가지정도의 문제가 있습니다.



이 문제를 해결하기위해 데코레이터 패턴을 쓰면 됩니다.



데코레이터패턴은 추상클래스가 존재하고

1. 추상클래스를 통해 음료클래스를 각각 만들고

2. 음료클래스를 상속받아 첨가물을 만드는 데코레이터 클래스를 만들고

3. 데코레이터를 첨가물들이 상속받아 만들게됩니다.


1. Espresso객체 를 가져오고

2. MoCha객체로 장식

3. Whip 객체로 장식

4. Soy 객체로 장식

5. Cost 메소드를 호출




기존코드가 abstract로 만들어진 추상클래스이기 때문에 기존소스를 최대한 수정하지 않고 추상클래스를 상속받아 구현하였습니다.


//음료의 상위클래스
abstract class Beverage() {
abstract fun getDescription(): String
abstract fun cost(): Double
}

//하우스 블랜드 커피
class HouseBlend() : Beverage() {
override fun getDescription(): String = "하우스 블렌드 커피"
override fun cost(): Double = 0.89
}

// 에스프레소
class Espresso() : Beverage() {
override fun getDescription(): String = "에스프레소"
override fun cost(): Double = 1.99
}

// 첨가물 데코레이터
abstract class CondimentDecorator : Beverage() {
}

class Mocha(val beverage: Beverage) : CondimentDecorator() {
override fun getDescription(): String = beverage.getDescription() + " 모카"
override fun cost(): Double = beverage.cost() + 0.20
}

class Soy(val beverage: Beverage) : CondimentDecorator() {
override fun getDescription(): String = beverage.getDescription() + " 두유"
override fun cost(): Double = beverage.cost() + 0.15
}

class Whip(val beverage: Beverage) : CondimentDecorator() {
override fun getDescription(): String = beverage.getDescription() + " 휘핑"
override fun cost(): Double = beverage.cost() + 0.10
}

Beverage() 클래스와 Beverage를 상속받은 두개의 HouseBlend, Espresso 그리고 

Beverage를 상속받고 데코레이터 클래스와을 상속받은 첨가물  soy whip mocha 클래스를 만듭니다.


var beverage: Beverage = Espresso()
beverage = Mocha(beverage)
beverage = Whip(beverage)
beverage = Soy(beverage)
println("description : ${beverage.getDescription()} cost : ${beverage.cost()}")


아까 말했던것과 같이 에스프레소클래스를 만들고

모카와 휘핑, 소이로 장식합니다.


그림으로 표현하게되면 

각각이 cost를 호출하게되고 합쳐진 가격으로 완성이 되게됩니다.

즉 에스프레소 1.99달러 + 모카 0.20 + 휘핑 0.10 + 소이 0.15 를 부르게됩니다.


더블 모카를 하기위해선 모카 데코레이터 클래스를 두번 호출하면 더블 모카가 완성됩니다.


위의 코드는 아까의 문제점이 사라진 오류의 코드지만 


저런형태로 관리하게 될경우 마지막 Soy를 빼먹는다던가 실수로 두번넣는 경우가 생기게됩니다.


따라서 다음포스팅에서는 팩토리 패턴과 빌터패턴을 이용해서 더 쉽게 구현이 가능하다고 하니 공부하고 포스팅하도록 하겠습니다.



-ps 과제 




enum class BeverageSize() {
VENTI, GRANDE, TALL
}

사이즈를 ENUM으로 구현하고


var beverage: Beverage = Espresso()
beverage.beverageSize = BeverageSize.GRANDE
beverage = Mocha(beverage)
beverage = Whip(beverage)
beverage = Soy(beverage)
println("description : ${beverage.getDescription()} cost : ${beverage.cost()}")

구현부에서 사이즈를 선택하게 합니다.


class Mocha(val beverage: Beverage) : CondimentDecorator() {
override fun getDescription(): String = beverage.getDescription() + " 모카"
override fun cost(): Double {
var price: Double = 0.0
when (beverage.beverageSize) {
BeverageSize.VENTI -> {
price = 0.20
}
BeverageSize.GRANDE -> {
price = 0.15
}
BeverageSize.TALL -> {
price = 0.1
}
}
return beverage.cost() + price
}
}

해당사이즈를 확인받아 음료의 가격과 함께 리턴합니다.