創建者模式-對象模版模式(The Object Template Pattern)

本文大部分內容翻譯至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些許修改,并將代碼升級到了Swift2.0,翻譯不當之處望多包涵。

對象模版模式

這里我們將介紹一個對于面向對象編程來說非常基礎的技術以至于不把它歸類到設計模式里面-用類或結構體創建新對象。


Swift中的元組是一系列的值組合在一起,用起來也很方便和簡單,但是它們卻有一些限制。下面我們創建Command Line Tool 工程,請看main.swift文件。

main.swift

import Foundation

var products = [
    ("Kayak", "A boat for one person", 275.0, 10),
    ("Lifejacket", "Protective and fashionable", 48.95, 14),
    ("Soccer Ball", "FIFA-approved size and weight", 19.5, 32)
]

func calculateTax(product:(String, String, Double, Int)) -> Double {
    return product.2 * 0.2
}

func calculateStockValue(tuples:[(String, String, Double, Int)]) -> Double {
        return tuples.reduce(0, combine: {
        (total, product) -> Double in
        return total + (product.2 * Double(product.3))
        })
}

print("Sales tax for Kayak: $\(calculateTax(products[0]))")
print("Total value of stock: $\(calculateStockValue(products))")

在上面的代碼中,我們定義了一個元素是元祖類型的數組代表產品以及兩個方法來操作它們。calculateTax方法接受一個元祖類型的參數用來計算價格的消費稅,calculateStockValue方法對數組中所有的產品進行總價計算。執行代碼,我們可以看見:

Sales tax for Kayak: $55.0
Total value of stock: $4059.3

設計模式要解決的問題大部分都是組件之間的緊密耦合。當一個在一個組件的內部去操作另一個組件時會產生緊密的耦合,或者,換一種說法,當你想變更一個組件而不需要去升級另一個組件才是設計模式所推崇和解決的。請看下圖:



兩個方法都跟元祖緊密的耦合在一起,不僅是它們定義參數的方式還是方法的內容。 當用元祖做參數的時候,元祖的大小,順序,類型都必須完全匹配。在方法中,又用元祖的下標來獲取值,這就導致更緊密的耦合。
下面你將看見當我刪掉元祖中一個值會發生什么:

import Foundation

var products = [
    ("Kayak", 275.0, 10),
    ("Lifejacket",  48.95, 14),
    ("Soccer Ball",  19.5, 32)
]

func calculateTax(product:(String, String, Double, Int)) -> Double {
    return product.2 * 0.2
}

func calculateStockValue(tuples:[(String, String, Double, Int)]) -> Double {
        return tuples.reduce(0, combine: {
        (total, product) -> Double in
        return total + (product.2 * Double(product.3))
        })
}

print("Sales tax for Kayak: $\(calculateTax(products[0]))")
print("Total value of stock: $\(calculateStockValue(products))")

我們刪掉了元祖中關于產品描述的值,但是很顯然我們現在跟著修改這兩個方法。



理解對象模版模式

對象模版模式利用一個類或者是結構體來定義一個能夠創建對象的模版。當組件需要一個對象時,它會通過指定模版的名稱來請求Swift運行環境創建并且運行環境會根據需要來初始化這個對象。



第一步就是組件提供模版名稱和一些要求的運行時數據請求Swift運行環境創建對象。 第二步,Swift運行環境會給要求的對象分配內存然后用模版來創建它,模版包含了用來設置對象的初始化狀態的初始化方法。最后一步Swift運行環境將創建好的對象交給請求的組件。


實現對象模版模式

Product.swift

import Foundation

class Product {
    var name:String
    var price:Double
    var stock:Int
    
    init(name:String, description:String, price:Double, stock:Int) {
        self.name = name
        self.description = description
        self.price = price
        self.stock = stock
    }
}

使用對象模版模式的好處

  • 解耦

我們將代碼做如下修改:

Product.swift

import Foundation

class Product {
    var name:String
    var description:String
    var price:Double
    var stock:Int
    
    init(name:String, price:Double, stock:Int) {
        self.name = name
        self.price = price
        self.stock = stock
    }
}

main.swift

import Foundation

var products = [
    Product(name: "Kayak", price: 275, stock: 10),
    Product(name: "Lifejacket", price: 48.95, stock: 14),
    Product(name: "Soccer Ball", price: 19.5, stock: 32)
]

func calculateTax(product:Product) -> Double {
    return product.price * 0.2;
}

func calculateStockValue(productsArray:[Product]) -> Double {
    return productsArray.reduce(0, combine: {(total, product) -> Double in
    return total + (product.price * Double(product.stock))
    })
}

我們更新了Product類將description屬性刪除了。最值得注意的是盡管我們修改了Product類,卻對calculateTax方法和calculateStockValue方法沒有任何影響。這是因為在Product類中每一個屬性都是獨立定義的并且這兩個方法也沒有操作description屬性。

  • 封裝

用類或者結構體來定義數據模版最大的好處就是封裝。封裝允許數值和操作這些數值的邏輯以一個簡單的方式聯系起來。將數值和邏輯聯系起來的話使得代碼更有可讀性。

Product.swift

import Foundation

class Product {
    var name:String
    var price:Double
    var stock:Int
    
    var stockValue:Double{
        
        return self.price * Double(self.stock)
    }
    
    init(name:String, price:Double, stock:Int) {
        self.name = name
        self.price = price
        self.stock = stock
    }

   func calculateTax(rate: Double) -> Double { 
       return self.price * rate
    }
}

main.swift

...

func calculateStockValue(productsArray:[Product]) -> Double {
    return productsArray.reduce(0, combine: {(total, product) -> Double in
    return total + product.stockValue
    })
}

...

這看著也許是簡單的修改,但是重要的事情發生了:Product類現在有了公有描述(public presentation)和私有實現(private implementation)。



公有描述就是其他組件能夠使用的API。任何組件都能設置和獲取name,price和stock屬性的值。公有描述也包括stockValue屬性和calculateTax方法,但重要的是注意并不是指它們的實現。
阻止一個屬性或者一個方法暴露它具體實現能夠很容易的打破耦合,因為這樣就使得其他組件的依賴變成不可能。例如我們將Proudct類中的calculateTax方法做下面的修改:

...
  func calculateTax(rate: Double) -> Double {
         return min(10, self.price * rate)
  }
...

因為修改的內容是在Product類中,這個修改對于其他的組件來說是不可見的,所以可以看出其他組件不會對Product類的實現產生依賴。


  • 進化的公有描述

Swift 中很重要的一個特型就是可以隨著應用的改變你也可以進化類的公有描述。就目前來看,stock屬性是一個可以設置任何Int類型值的存儲屬性,但是對于庫存來說負數是沒有任何意義的。Swift支持我們能夠無縫的將存儲屬性修改成計算屬性。

import Foundation
class Product {
    var name:String
    var price:Double
    
    private var stockBackingValue:Int = 0
    
    var stock:Int {
        get {
              return stockBackingValue
        }
        
        set {
             stockBackingValue = max(0, newValue)
        }
    }
    
    var stockValue:Double{
        
        return self.price * Double(self.stock)
    }
    
    init(name:String, price:Double, stock:Int) {
        self.name = name
        self.price = price
        self.stock = stock
    }
    
    func calculateTax(rate: Double) -> Double {
        return min(10,self.price * rate)
    }
}

我們定義了一個 stockBackingValue存儲屬性的變量來儲存計算屬性變量stock的值,stock的get方法僅僅是簡單的返回stockBackingValue的值,但是set方法去用了max方法來保證當設定值是負數的時候用0來代替。



現在修改main.swift如下

main.swift

import Foundation

var products = [
    Product(name: "Kayak", price: 275, stock: 10),
    Product(name: "Lifejacket", price: 48.95, stock: 14),
    Product(name: "Soccer Ball", price: 19.5, stock: 32)
]

func calculateTax(product:Product) -> Double {
    return product.price * 0.2;
}

func calculateStockValue(productsArray:[Product]) -> Double {
    return productsArray.reduce(0, combine: {(total, product) -> Double in
    return total + product.stockValue
    })
}
print("Sales tax for Kayak: $\(products[0].calculateTax(0.2))")
print("Total value of stock: $\(calculateStockValue(products))")
products[0].stock = -50
print("Stock Level for Kayak: \(products[0].stock)")

運行程序,得到下面結果:

Sales tax for Kayak: $10.0
Total value of stock: $4059.3
Stock Level for Kayak: 0

理解對象模版模式的陷阱

對象模版模式需要注意的陷阱就是模版的類型選擇,好比當用類更合適的時候卻使用了結構體。Swift的類和結構體有很多共性,但尤其注意的是結構體是值類型,類是引用類型。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,158評論 4 61
  • 那條路就在你的身后,你可以前行或往回走,挽留的話,我沒有說出口,你的年齡早已熟透,也許你的字典里沒有什么人或...
    懂我笑容任閱讀 219評論 0 0
  • 最近做的項目使用了NSThread來開啟一個線程來保持與設備的連接,然而當手機與設備斷開連接且需要釋放掉該線程的時...
    行者之心閱讀 433評論 0 0
  • 你是否也會遇到這樣的窘境: 明明心中很想奮斗,清單上列了一堆想做的事情和學習的技能,細細想來卻發現自己什么都沒有完...
    正記錄Beta閱讀 5,054評論 6 25
  • 最近沒有太多讓人值得高興的事情 生日也就簡單過一次 24 歲好像知道身上責任有多重 真的是一天一天就變老了
    RayThinking閱讀 226評論 0 0