thinking in Swift:重新審視裝飾器模式

發表於2016-10-05
11225849-31f73c10d057d35d

如果在swift中按部就班的談Gof設計模式,這在一開始就是錯誤的命題。原因主要有兩個:

  • 設計模式是基於物件導向的程式設計正規化
  • 實現基於當時的主流程式語言:C++ 和 Java

如今的swift的推薦程式設計正規化並不是物件導向,很多人都大談面向協議、函數語言程式設計我就不展開了;現代的swift中有一些語法特性是當時的語言所不具備的,比如protocol extension,高階函式等。

所以本文將利用swift的語法來談下裝飾器模式在swift下的解決思路。

Decorator pattern

有人翻作裝飾器模式,也有翻成裝飾者模式,英文的名稱就是Decorator pattern

裝飾器模式能夠實現動態的為物件新增功能,是從一個物件外部來給物件新增功能。裝飾器模式就是基於物件組合的方式,可以很靈活的給物件新增所需要的功能。

直接用程式碼來說明。

用Dish表示菜餚,一道菜有兩個屬性,名稱和價格。然後為了方便測試重寫了description屬性,返回名稱和價格。

再假設有一個菜的訂單物件,接收一個Dish物件後,最後可以通過total方法返回這些菜的總價錢。具體就不實現了,大概這個邏輯。

假設在一個飯館裡,老闆發現這裡的客人點菜的時候喜歡讓廚師多放點鹽,大家都知道非典時期鹽很貴,所以老闆覺得很虧,決定如果一道菜多加鹽就要貴一塊錢。接著又來了一個需求,如果打包帶走,一道菜再加兩塊錢。如果我們不能改變Dish的原始碼(在實際專案中常會遇到這種情況,可能這類定義在第三方的庫裡),要怎麼實現這兩個需求呢?

可以定義兩個裝飾器,注意這兩個裝飾器都要繼承Dish:

現在我們要表示一道打包帶走的松鼠桂魚就這樣表示了:

12225849-bc603d5afe57bec9

這樣我們就可以給任意一道菜增加一些裝飾性的功能。也有人用咖啡舉例子,一杯咖啡可能要加糖,加奶,加巧克力等等。一層包一層。如果取名字的是中國人可能就叫洋蔥模式了。最後使用的時候行為和Dish是一樣的。因為這些Decorator是繼承自Dish的。只是在初始化過程中改變了原有的一些屬性。

缺陷:繼承不是一個優秀的解決方案

程式設計時常常提到的一個指導思想就是組合優於繼承。

繼承最大的問題就在於你只能有一個爹。一個爹的結果就是能力有限,不夠靈活。所以最後還得認一些乾爹。

就拿上面的例子來講,現在是給Dish做了幾個裝飾功能,如果有一天說店裡的點心也要支援這兩個功能(給我來一個加鹽的饅頭!),是不是有要繼承點心類寫兩個裝飾器呢?

利用Swift中的Extension

我們可以這麼理解這個需求,需要有這麼一個方法,接受一個Dish型別的引數,經過處理後返回一個Dish。我們完全可以把這個方法通過extension寫在Dish身上。

然後我們就可以鏈式呼叫:

更進一步:protocol extension

如果為了將來的擴充套件靈活,也可以把這個裝飾寫到protocol的extension裡。

上面先定義了一個產品的協議,有名稱和價格兩個屬性。
接著再定義了一個繼承Product的Salteable的協議。裡面有一個返回自身的salted方法。接著給這個protocol增加擴充套件實現:

然後我們再定義一個表示小吃的Snack,和菜一樣也有兩個屬性。

如果我們要給這個Snack增加加鹽的效果,只要宣告他實現Salteable協議就可以了。

這樣就夠啦!
看下輸出:

13225849-8c400c2dd49fc914

歡迎關注我的微博:@沒故事的卓同學

相關文章