本文大部分內(nèi)容翻譯至《Pro Design Pattern In Swift》By Adam Freeman鲁沥,一些地方做了些許修改祝沸,并將代碼升級(jí)到了Swift2.0,翻譯不當(dāng)之處望多包涵笙什。
對(duì)象模版模式
這里我們將介紹一個(gè)對(duì)于面向?qū)ο缶幊虂碚f非撑骞龋基礎(chǔ)的技術(shù)以至于不把它歸類到設(shè)計(jì)模式里面-用類或結(jié)構(gòu)體創(chuàng)建新對(duì)象屈嗤。
Swift中的元組是一系列的值組合在一起倦淀,用起來也很方便和簡(jiǎn)單,但是它們卻有一些限制卒暂。下面我們創(chuàng)建Command Line Tool 工程啄栓,請(qǐng)看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))")
在上面的代碼中也祠,我們定義了一個(gè)元素是元祖類型的數(shù)組代表產(chǎn)品以及兩個(gè)方法來操作它們昙楚。calculateTax方法接受一個(gè)元祖類型的參數(shù)用來計(jì)算價(jià)格的消費(fèi)稅,calculateStockValue方法對(duì)數(shù)組中所有的產(chǎn)品進(jìn)行總價(jià)計(jì)算诈嘿。執(zhí)行代碼堪旧,我們可以看見:
Sales tax for Kayak: $55.0
Total value of stock: $4059.3
設(shè)計(jì)模式要解決的問題大部分都是組件之間的緊密耦合。當(dāng)一個(gè)在一個(gè)組件的內(nèi)部去操作另一個(gè)組件時(shí)會(huì)產(chǎn)生緊密的耦合奖亚,或者淳梦,換一種說法,當(dāng)你想變更一個(gè)組件而不需要去升級(jí)另一個(gè)組件才是設(shè)計(jì)模式所推崇和解決的昔字。請(qǐng)看下圖:
兩個(gè)方法都跟元祖緊密的耦合在一起爆袍,不僅是它們定義參數(shù)的方式還是方法的內(nèi)容。 當(dāng)用元祖做參數(shù)的時(shí)候作郭,元祖的大小陨囊,順序,類型都必須完全匹配夹攒。在方法中蜘醋,又用元祖的下標(biāo)來獲取值,這就導(dǎo)致更緊密的耦合咏尝。
下面你將看見當(dāng)我刪掉元祖中一個(gè)值會(huì)發(fā)生什么:
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))")
我們刪掉了元祖中關(guān)于產(chǎn)品描述的值压语,但是很顯然我們現(xiàn)在跟著修改這兩個(gè)方法。
理解對(duì)象模版模式
對(duì)象模版模式利用一個(gè)類或者是結(jié)構(gòu)體來定義一個(gè)能夠創(chuàng)建對(duì)象的模版编检。當(dāng)組件需要一個(gè)對(duì)象時(shí)无蜂,它會(huì)通過指定模版的名稱來請(qǐng)求Swift運(yùn)行環(huán)境創(chuàng)建并且運(yùn)行環(huán)境會(huì)根據(jù)需要來初始化這個(gè)對(duì)象。
第一步就是組件提供模版名稱和一些要求的運(yùn)行時(shí)數(shù)據(jù)請(qǐng)求Swift運(yùn)行環(huán)境創(chuàng)建對(duì)象蒙谓。 第二步,Swift運(yùn)行環(huán)境會(huì)給要求的對(duì)象分配內(nèi)存然后用模版來創(chuàng)建它训桶,模版包含了用來設(shè)置對(duì)象的初始化狀態(tài)的初始化方法累驮。最后一步Swift運(yùn)行環(huán)境將創(chuàng)建好的對(duì)象交給請(qǐng)求的組件。
實(shí)現(xiàn)對(duì)象模版模式
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
}
}
使用對(duì)象模版模式的好處
-
解耦
我們將代碼做如下修改:
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類谤专,卻對(duì)calculateTax方法和calculateStockValue方法沒有任何影響。這是因?yàn)樵赑roduct類中每一個(gè)屬性都是獨(dú)立定義的并且這兩個(gè)方法也沒有操作description屬性午绳。
-
封裝
用類或者結(jié)構(gòu)體來定義數(shù)據(jù)模版最大的好處就是封裝置侍。封裝允許數(shù)值和操作這些數(shù)值的邏輯以一個(gè)簡(jiǎn)單的方式聯(lián)系起來。將數(shù)值和邏輯聯(lián)系起來的話使得代碼更有可讀性。
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
})
}
...
這看著也許是簡(jiǎn)單的修改蜡坊,但是重要的事情發(fā)生了:Product類現(xiàn)在有了公有描述(public presentation)和私有實(shí)現(xiàn)(private implementation)杠输。
公有描述就是其他組件能夠使用的API。任何組件都能設(shè)置和獲取name秕衙,price和stock屬性的值蠢甲。公有描述也包括stockValue屬性和calculateTax方法,但重要的是注意并不是指它們的實(shí)現(xiàn)据忘。
阻止一個(gè)屬性或者一個(gè)方法暴露它具體實(shí)現(xiàn)能夠很容易的打破耦合鹦牛,因?yàn)檫@樣就使得其他組件的依賴變成不可能。例如我們將Proudct類中的calculateTax方法做下面的修改:
...
func calculateTax(rate: Double) -> Double {
return min(10, self.price * rate)
}
...
因?yàn)樾薷牡膬?nèi)容是在Product類中勇吊,這個(gè)修改對(duì)于其他的組件來說是不可見的曼追,所以可以看出其他組件不會(huì)對(duì)Product類的實(shí)現(xiàn)產(chǎn)生依賴。
-
進(jìn)化的公有描述
Swift 中很重要的一個(gè)特型就是可以隨著應(yīng)用的改變你也可以進(jìn)化類的公有描述汉规。就目前來看礼殊,stock屬性是一個(gè)可以設(shè)置任何Int類型值的存儲(chǔ)屬性,但是對(duì)于庫存來說負(fù)數(shù)是沒有任何意義的鲫忍。Swift支持我們能夠無縫的將存儲(chǔ)屬性修改成計(jì)算屬性膏燕。
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)
}
}
我們定義了一個(gè) stockBackingValue存儲(chǔ)屬性的變量來儲(chǔ)存計(jì)算屬性變量stock的值,stock的get方法僅僅是簡(jiǎn)單的返回stockBackingValue的值悟民,但是set方法去用了max方法來保證當(dāng)設(shè)定值是負(fù)數(shù)的時(shí)候用0來代替坝辫。
現(xiàn)在修改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)")
運(yùn)行程序,得到下面結(jié)果:
Sales tax for Kayak: $10.0
Total value of stock: $4059.3
Stock Level for Kayak: 0
理解對(duì)象模版模式的陷阱
對(duì)象模版模式需要注意的陷阱就是模版的類型選擇射亏,好比當(dāng)用類更合適的時(shí)候卻使用了結(jié)構(gòu)體近忙。Swift的類和結(jié)構(gòu)體有很多共性,但尤其注意的是結(jié)構(gòu)體是值類型智润,類是引用類型及舍。