您可於此 design_pattern repo 下載 Design Pattern 系列程式碼。
需求
我們收到了一個需求:咖啡店的 POS 系統需要計算不同咖啡及其附加選項(如牛奶、糖漿、奶泡等)的價格。具體需求如下:
- 咖啡種類包括基本款的 Espresso 和 House Blend。
- 每種咖啡都可以加不同的附加項,例如牛奶、巧克力糖漿、奶泡。
- 系統應該支持動態組合不同的附加項,而不需要針對每種組合定義類別。
物件導向分析 (OOA)
理解需求後,讓我們來快速實作物件導向分析吧!
察覺 Forces
在未使用設計模式的情況下,上述需求可能會遇到以下問題:
-
類別爆炸 (Class Explosion):
- 為每一種咖啡及其附加選項組合創建類別,導致類別數量迅速增長。
-
高耦合性 (Tight Coupling):
- 咖啡與附加選項緊密耦合,修改某一部分時可能影響整體。
-
靈活性差 (Lack of Flexibility):
- 系統無法動態地添加或移除附加選項,只能依賴預先定義的組合。
-
重複代碼 (Code Duplication):
- 每種組合的實作邏輯重複,導致維護困難。
套用 Decorator Pattern (Solution) 得到新的 Context (Resulting Context)
做完 OOA,察覺 Forces,看清楚整個 Context 後,就可以來套用 Decorator Pattern 解決這個問題。
先來看一下 Decorator Pattern 的 UML:
- Component (組件介面):定義基本的行為或功能。
- ConcreteComponent (具體組件):實作基本功能的具體類別,例如基本咖啡。
- Decorator (裝飾者介面):維護對 Component 的引用,並在此基礎上添加新功能。
- ConcreteDecorator (具體裝飾者):實作新增的功能,例如牛奶、糖漿等附加選項。
將 Decorator Pattern 套用到我們的應用吧
物件導向程式設計 (OOP)
[Component: Beverage]
interface Beverage {
val description: String
fun cost(): Double
}
[ConcreteComponent: Espresso and HouseBlend]
class Espresso : Beverage {
override val description = "Espresso"
override fun cost() = 1.99
}
class HouseBlend : Beverage {
override val description = "House Blend Coffee"
override fun cost() = 0.89
}
[Decorator: CondimentDecorator]
abstract class CondimentDecorator(protected val beverage: Beverage) : Beverage() {
override abstract val description: String
}
[ConcreteDecorator: Milk, Mocha, and Whip]
class Milk(beverage: Beverage) : CondimentDecorator(beverage) {
override val description = "${beverage.description}, Milk"
override fun cost() = beverage.cost() + 0.3
}
class ChocolateSyrup(beverage: Beverage) : CondimentDecorator(beverage) {
override val description = "${beverage.description}, Chocolate Syrup"
override fun cost() = beverage.cost() + 0.5
}
class WhippedCream(beverage: Beverage) : CondimentDecorator(beverage) {
override val description = "${beverage.description}, Whipped Cream"
override fun cost() = beverage.cost() + 0.4
}
[Client]
fun main() {
// Make an Espresso
val espresso = Espresso()
println("${espresso.description}: $${espresso.cost()}")
// Make an Espresso with Milk、Chocolate Syrup and Whipped Cream
val customBeverage = WhippedCream(
ChocolateSyrup(
Milk(Espresso())
)
)
println("${customBeverage.description}: $${customBeverage.cost()}")
// Make an HouseBlend with Milk and double Whipped Cream
val layeredBeverage = WhippedCream(
WhippedCream(
Milk(HouseBlend())
)
)
println("${layeredBeverage.description}: $${layeredBeverage.cost()}")
}
[Output]
Espresso: $1.99
Espresso, Milk, Chocolate Syrup, Whipped Cream: $3.19
House Blend, Milk, Whipped Cream, Whipped Cream: $2.49
結論
通過套用 Decorator Pattern,我們成功動態地為物件添加新功能,並保持類別數量的可控性。此模式不僅提供了靈活的擴展方式,還減少了重複代碼和耦合性,使得系統更易於維護和擴展。透過裝飾者模式,開發者可以更高效地應對需求變更,滿足複雜功能的實現。
Leave a comment