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

若是在swift中循序漸進的談Gof設計模式,這在一開始就是錯誤的命題。緣由主要有兩個:編程

  • 設計模式是基於面向對象的編程範式
  • 實現基於當時的主流編程語言:C++ 和 Java

現在的swift的推薦編程範式並非面向對象,不少人都大談面向協議、函數式編程我就不展開了;現代的swift中有一些語法特性是當時的語言所不具有的,好比protocol extension,高階函數等。swift

因此本文將利用swift的語法來談下裝飾器模式在swift下的解決思路。設計模式

Decorator pattern

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

裝飾器模式可以實現動態的爲對象添加功能,是從一個對象外部來給對象添加功能。裝飾器模式就是基於對象組合的方式,能夠很靈活的給對象添加所須要的功能。編程語言

直接用代碼來講明。函數式編程

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

class Dish: CustomStringConvertible {

    var name: String
    var price: Int

    init(name: String, price: Int) {
        self.name = name
        self.price = price
    }

    var description: String {
        get {
            return "\(name): \(price)元"
        }
    }
}複製代碼

再假設有一個菜的訂單對象,接收一個Dish對象後,最後能夠經過total方法返回這些菜的總價錢。具體就不實現了,大概這個邏輯。測試

class DishOrder {

    func append(dish: Dish) {

    }

    func total() -> Int {

    }
}複製代碼

假設在一個飯館裏,老闆發現這裏的客人點菜的時候喜歡讓廚師多放點鹽,你們都知道非典時期鹽很貴,因此老闆以爲很虧,決定若是一道菜多加鹽就要貴一塊錢。接着又來了一個需求,若是打包帶走,一道菜再加兩塊錢。若是咱們不能改變Dish的源碼(在實際項目中常會遇到這種狀況,可能這類定義在第三方的庫裏),要怎麼實現這兩個需求呢?spa

能夠定義兩個裝飾器,注意這兩個裝飾器都要繼承Dish:設計

// 加鹽的裝飾器
class SaltDishDecorator: Dish {

    init(dish: Dish) {
        super.init(name:"加糖 \(dish.name)", price: dish.price + 1)
    }

}

// 外帶打包的裝飾器
class PackageDishDecorator: Dish {

    init(dish: Dish) {
        super.init(name:"打包 \(dish.name)", price: dish.price + 2)
    }

}複製代碼

如今咱們要表示一道打包帶走的松鼠桂魚就這樣表示了:

let dish = PackageDishDecorator(dish: SaltDishDecorator(dish: Dish(name: "松鼠桂魚", price: 15)))複製代碼

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

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

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

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

就拿上面的例子來說,如今是給Dish作了幾個裝飾功能,若是有一天說店裏的點心也要支持這兩個功能(給我來一個加鹽的饅頭!),是否是有要繼承點心類寫兩個裝飾器呢?

利用Swift中的Extension

咱們能夠這麼理解這個需求,須要有這麼一個方法,接受一個Dish類型的參數,通過處理後返回一個Dish。咱們徹底能夠把這個方法經過extension寫在Dish身上。

extension Dish {

    func salted() -> Dish {
        return Dish(name:"加鹽 \(name)", price: price + 1)
    }

    func packaged() -> Dish {
        return Dish(name:"打包 \(name)", price: price + 2)
    }
}複製代碼

而後咱們就能夠鏈式調用:

let extenedDish = Dish(name: "松鼠桂魚", price: 15).salted().packaged()複製代碼

更進一步:protocol extension

若是爲了未來的擴展靈活,也能夠把這個裝飾寫到protocol的extension裏。

protocol Product {
    var name: String { get set }
    var price: Int { get set }
}

protocol Salteable: Product {
     func salted() -> Self
}複製代碼

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

extension Salteable {
  func salted() -> Self {
        var newProduct = self
        newProduct.name = "加鹽 \(name)"
        newProduct.price = price + 1
        return newProduct
    }
}複製代碼

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

struct Snack: CustomStringConvertible {

    var name: String
    var price: Int

    init(name: String, price: Int) {
        self.name = name
        self.price = price
    }

    var description: String {
        get {
            return "\(name): \(price)元"
        }
    }
}複製代碼

若是咱們要給這個Snack增長加鹽的效果,只要聲明他實現Salteable協議就能夠了。

extension Snack: Salteable {

}複製代碼

這樣就夠啦! 看下輸出:

歡迎關注個人微博:@沒故事的卓同窗

相關文章
相關標籤/搜索