還記得Stone City之前那家有名的Stone Bakery嗎?我們當時利用觀察者模式給他們設計了一套通知系統。
在後來的經營過程中,他們深深的感受到科技帶來的好處,於是這次他們又來找我們了,希望我們能夠設計一套便捷的下單系統。
下單系統的設計
創建一個Purchase類,Purchase中可以選擇商品並且輸入價格:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Purchase { private let product:String private let price:Float init(product:String, price:Float) { self.product = product self.price = price } var description:String { return product } var totalPrice:Float { return price } } |
接著我們為客戶建立一個CustomerAccount類別,這個類別中包含了:
- 客戶的名稱
- 購買的商品(用Purchase類表示)
- 增加商品的方法addPurchase()
- 顯示目前購物車中的內容showAccount()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class CustomerAccount { let name:String var purchases = [Purchase]() init(name:String) { self.name = name } func addPurchase(purchase:Purchase) { self.purchases.append(purchase) } func showAccount() { var total:Float = 0 print("購物車中包含:") for item in purchases { total += item.totalPrice print("商品 \(item.description) 價格 \(formatCurrencyString(number: item.totalPrice))") } } func formatCurrencyString(number:Float) -> String { let formatter = NumberFormatter() formatter.numberStyle = .currency return formatter.string(from: NSNumber(value: number))! } } |
這樣一個簡單的系統就完成了,通過實例化CustomerAccount,我們就可以向account中加入商品。
最後通過account.showAccount方法來顯示購物車中的商品內容。
1 2 3 4 5 6 7 |
let account = CustomerAccount(name: "Don") account.addPurchase(purchase: Purchase(product: "Sandwich", price: 10)) account.addPurchase(purchase: Purchase(product: "Coffee", price: 20)) account.addPurchase(purchase: Purchase(product: "Chocolate", price: 30)) account.showAccount() |
結果顯示:
1 2 3 4 |
購物車中包含: 商品 Sandwich 價格 $10.00 商品 Coffee 價格 $20.00 商品 Chocolate 價格 $30.00 |
Stone Bakery的老闆很高興,不過他之前去Wood City和Wood Bakery老闆交流很多,
他發現Wood Bakery賣的不僅僅是麵包甜點,他們同時提供包裝、運送的服務,將麵包甜點變成一個非常適合用來送禮的商品。
所以,Stone Bakery的老闆又來找我們了,他希望能夠加入更多的服務項目來讓客戶掏錢,喔不是,是讓客戶開心啦(hahahhahah….)
下單系統的強化
我們創建一個Services類,用來提供商品以外的服務,分別是:
- PurchaseWithDelivery()
- PurchaseWithGiftWrap()
- PurchaseWithCard()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class PurchaseWithDelivery: Purchase { override var description:String { return "\(super.description) + 運送" } override var totalPrice:Float { return super.totalPrice + 5 } } class PurchaseWithGiftWrap: Purchase { override var description:String { return "\(super.description) + 禮盒包裝" } override var totalPrice:Float { return super.totalPrice + 7.5 } } class PurchaseWithCard: Purchase { override var description:String { return "\(super.description) + 卡片" } override var totalPrice: Float { return super.totalPrice + 3.5 } } |
我們試試看再購買Chocolate的時候加入運輸服務:
1 2 3 4 5 6 7 8 |
let account = CustomerAccount(name: "Don") account.addPurchase(purchase: Purchase(product: "Sandwich", price: 10)) account.addPurchase(purchase: Purchase(product: "Coffee", price: 20)) account.addPurchase(purchase: PurchaseWithDelivery(product: "Chocolate", price: 10)) account.showAccount() |
看來沒問題,當購買加入Chocolate的時候會加上運輸費用5元。
1 2 3 4 |
購物車中包含: 商品 Sandwich 價格 $10.00 商品 Coffee 價格 $20.00 商品 Chocolate + 運送 價格 $15.00 |
但寫到這裡,總覺得哪裡怪怪的………沒錯!萬一有個客戶同時需要的服務是混搭的呢?
- chocolate + 運輸 + 卡片
- chocolate + 運輸 + 包裝
- chocolate + 運輸 + 卡片 + 包裝
- chocolate + 運輸 + 包裝
- …. …
…….看來可能性很多阿,一旦提供的服務開始多起來,我猜光是寫各種搭配就要寫很久了Orz…..
該怎麼辦呢?
裝飾者模式(Decorator Pattern)
裝飾器模式可以選擇性的修改對象的行為,可以使用的場景很多,尤其是在無法修改類的時候。
在擴展方面裝飾者模式比生成子類更靈活。
- 何時應該避免使用:如果可以修改想要修改的對象的類,這時候往往直接修改更簡單。
- 陷阱:最容易碰到的陷阱就是直接使用Swift的Extension來裝飾對象。
裝飾器的一個主要特徵是「選擇性」的應用到單個對象上,而Extension則是修改所有某個類型的對象。
我們首先在Services.swift文件中,創建BasePurchaseDecorator用來繼承無法修改的Purchase類,並且讓那些服務都來繼承BasePurchaseDecorator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
class PurchaseWithDelivery: BasePurchaseDecorator { override var description:String { return "\(super.description) + 運送" } override var totalPrice:Float { return super.totalPrice + 5 } } class PurchaseWithGiftWrap: BasePurchaseDecorator { override var description:String { return "\(super.description) + 禮盒包裝" } override var totalPrice:Float { return super.totalPrice + 7.5 } } class PurchaseWithCard: BasePurchaseDecorator { override var description:String { return "\(super.description) + 卡片" } override var totalPrice: Float { return super.totalPrice + 3.5 } } class BasePurchaseDecorator: Purchase { private let wrappedPurchase: Purchase init(purchase:Purchase) { wrappedPurchase = purchase super.init(product: purchase.description, price: purchase.totalPrice) } } |
我們來試試看,購買一個價格為30元的Cholate,並且購買包裝和運輸的服務,調用的過程就像上圖一樣。
1 2 3 4 5 6 |
account.addPurchase(purchase: Purchase(product: "Sandwich", price: 10)) account.addPurchase(purchase: Purchase(product: "Coffee", price: 20)) account.addPurchase(purchase: PurchaseWithGiftWrap(purchase: PurchaseWithDelivery(purchase: Purchase(product: "Chocolate", price: 30)))) account.showAccount() |
馬上就可以看到我們要的效果咯:
1 2 3 4 |
商品 Sandwich 價格 $10.00 商品 Coffee 價格 $20.00 商品 Chocolate 價格 $30.00 商品 Chocolate + 運送 + 禮盒包裝 價格 $22.50 |
看來新服務上線,大家都來體驗啦,可以到Github上看看次的裝飾者模式例子。