引言
GoF的《設(shè)計(jì)模式》一書總結(jié)了面向?qū)ο筌浖幸恍氋F的設(shè)計(jì)經(jīng)驗(yàn)乱凿,系統(tǒng)地對(duì)它們命名徒蟆、解釋和評(píng)價(jià)段审,并以編目分類的形式將它們展現(xiàn)出來,這就是廣為流傳的23個(gè)設(shè)計(jì)模式的由來型凳。
模式是一項(xiàng)管理復(fù)雜度的技術(shù)甘畅,幾乎所有模式都遵循兩個(gè)原則:
- 針對(duì)接口編程蓄氧,而不是實(shí)現(xiàn)喉童。
- 多用組合堂氯,少用繼承。
很多模式看上去很類似晶框,這是因?yàn)閷?shí)現(xiàn)各種模式的方式一般就是繼承和組合。對(duì)外暴露一個(gè)通用接口畴蒲,既易于使用又隱藏實(shí)現(xiàn)細(xì)節(jié),內(nèi)部用各種子類來實(shí)現(xiàn)不同功能蔫骂,支持?jǐn)U展變化辽旋,并盡量用對(duì)象組合來實(shí)現(xiàn)解耦。所以你可以認(rèn)為23個(gè)模式就是根據(jù)不同的使用場景變著法兒地聲明接口然后繼承實(shí)現(xiàn)最后再花式組合罷了溶其。
模式依據(jù)其設(shè)計(jì)目的可以分為三大類——?jiǎng)?chuàng)建型(Creational)束铭、結(jié)構(gòu)型(Structural)和行為型(Behavioral)。本文主要論述幾個(gè)創(chuàng)建型模式之間的區(qū)別與聯(lián)系懈万。
《設(shè)計(jì)模式》一書的副標(biāo)題是“可復(fù)用面向?qū)ο筌浖幕A(chǔ)”嫡秕,所以顯然這23個(gè)設(shè)計(jì)模式是用于面向?qū)ο筌浖O(shè)計(jì)的昆咽,而眾所周知窟哺,最適合面向?qū)ο筮@種范式的領(lǐng)域其實(shí)是 GUI 編程領(lǐng)域(這也是《設(shè)計(jì)模式》中大部分應(yīng)用實(shí)例都是一些 GUI 框架的原因)浮声,所以本文也主要以 iOS 開發(fā)為例進(jìn)行說明。示例語言選用靜態(tài)語言 Swift(其實(shí)個(gè)人認(rèn)為設(shè)計(jì)模式主要還是針對(duì)靜態(tài)語言屉符,很多模式在動(dòng)態(tài)語言中都用處不大)。
創(chuàng)建型模式簡介
創(chuàng)建型模式將實(shí)例化對(duì)象的部分從系統(tǒng)中獨(dú)立出來,它們將系統(tǒng)具體使用哪些類的信息封裝起來虑凛,并隱藏了這些類是如何被創(chuàng)建和組合的,對(duì)外只提供一個(gè)通用接口。
創(chuàng)建型模式有五種——Abstract Factory(抽象工廠)、Builder(生成器)胧辽、Factory Method(工廠方法)凡蚜、Prototype(原型)、Singleton(單例)。我個(gè)人認(rèn)為抽象工廠模式和生成者模式的抽象層級(jí)最高,因?yàn)樗鼈兌伎梢苑謩e用工廠方法和原型實(shí)現(xiàn)。而工廠方法和原型是同一個(gè)層級(jí)的,它們?cè)诖蠖鄶?shù)時(shí)候是互斥的驱还,一般不能結(jié)合使用闷沥。至于單例,就是保證某個(gè)類只實(shí)例化一次而已,想用在哪兒都行(只要符合實(shí)際需求)展蒂。
抽象工廠側(cè)重于創(chuàng)建一系列同一風(fēng)格的產(chǎn)品团赏,每個(gè)產(chǎn)品都有一個(gè)抽象接口杯聚,使用者并不知道它使用的是具體哪種風(fēng)格的產(chǎn)品抒痒。而生成器側(cè)重于一步步構(gòu)建一個(gè)復(fù)雜產(chǎn)品幌绍,這個(gè)復(fù)雜產(chǎn)品不需要有一個(gè)公共接口,使用者知道它具體得到了一個(gè)什么產(chǎn)品故响。
抽象方法定義一個(gè)用于創(chuàng)建對(duì)象的接口傀广,子類重寫創(chuàng)建方法,被創(chuàng)建的產(chǎn)品會(huì)有一個(gè)抽象接口彩届,所以使用者并不知道具體得到的是什么產(chǎn)品伪冰。原型將某個(gè)實(shí)例對(duì)象作為“原型”,通過復(fù)制這個(gè)原型來創(chuàng)建新的對(duì)象樟蠕,由于可以動(dòng)態(tài)指定原型贮聂,所以可以在運(yùn)行期改變創(chuàng)建的產(chǎn)品靠柑。
一個(gè)簡單案例
假設(shè)我們現(xiàn)在要構(gòu)建兩個(gè)界面,界面的構(gòu)成元素都是一個(gè) Label 和 Button吓懈。一個(gè)界面在打開應(yīng)用的時(shí)候顯示病往,Label 和 Button 會(huì)顯示“Hello……”,另一個(gè)在應(yīng)用關(guān)閉前顯示骄瓣,Label 和 Button 會(huì)顯示“Goodbye……”停巷。而且不止是顯示的文字,連同背景色榕栏、位置畔勤、大小等等屬性都會(huì)不同。于是我們考慮自定義幾個(gè) Label 和 Button:
//MARK: - Hello 系列產(chǎn)品
class HelloButton: UIButton {
init() {
let frame = CGRect(x: 50, y: 200, width: 300, height: 50)
super.init(frame: frame)
backgroundColor = UIColor.greenColor()
setTitle("Hello, I am a button.", forState: .Normal)
setTitleColor(UIColor.redColor(), forState: .Normal)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class HelloLabel: UILabel {
init() {
let frame = CGRect(x: 50, y: 300, width: 300, height: 50)
super.init(frame: frame)
backgroundColor = UIColor.yellowColor()
text = "Hello, I am a label."
textAlignment = .Center
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//MARK: - Goodbye 系列產(chǎn)品
class GoodbyeButton: UIButton {
init() {
let frame = CGRect(x: 50, y: 400, width: 300, height: 20)
super.init(frame: frame)
backgroundColor = UIColor.redColor()
setTitle("Goodbye, don't forget I'm a button.", forState: .Normal)
setTitleColor(UIColor.greenColor(), forState: .Normal)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class GoodbyeLabel: UILabel {
init() {
let frame = CGRect(x: 100, y: 500, width: 200, height: 100)
super.init(frame: frame)
backgroundColor = UIColor.blackColor()
text = "Goodbye, don't forget I'm a label."
textColor = UIColor.whiteColor()
font = UIFont.systemFontOfSize(10)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
然后我們定義一個(gè)用來組合界面視圖的類:
class PageView: UIView {
init() {
super.init(frame: UIScreen.mainScreen().bounds)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
稍后我會(huì)介紹用不同的模式來創(chuàng)建產(chǎn)品扒磁。
Factory Method(工廠方法)
現(xiàn)在我們給 PageView 加上用來創(chuàng)建 Label 和 Button 的工廠方法庆揪,并在構(gòu)造器中調(diào)用工廠方法。工廠方法可以是抽象方法也可以有一個(gè)默認(rèn)實(shí)現(xiàn)妨托,這里我給出一個(gè)默認(rèn)實(shí)現(xiàn):
class PageView: UIView {
init() {
super.init(frame: UIScreen.mainScreen().bounds)
let label = createLabel()
let button = createButton()
addSubview(label)
addSubview(button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createLabel() -> UILabel {
return UILabel()
}
func createButton() -> UIButton {
return UIButton()
}
}
這樣我們要?jiǎng)?chuàng)建 HelloPageView 和 GoodbyepageView 的時(shí)候只要重寫工廠方法就好了:
class HelloPageView: PageView {
override func createLabel() -> UILabel {
return HelloLabel()
}
override func createButton() -> UIButton {
return HelloButton()
}
}
class GoodbyePageView: PageView {
override func createLabel() -> UILabel {
return GoodbyeLabel()
}
override func createButton() -> UIButton {
return GoodbyeButton()
}
}
常規(guī)的工廠方法有個(gè)顯而易見的缺點(diǎn)就是當(dāng)需要進(jìn)行不同產(chǎn)品的組合的時(shí)候缸榛,容易導(dǎo)致類爆炸。譬如現(xiàn)在我們只是需要“HelloLabel + HelloButton”和“GoodByeLabel + GoodbyeButton”兰伤,但如果我們還需要“ HelloLabel + GoodbyeButton”和“ GoodByeLabel + HelloButton”這樣的組合内颗,那就又得新建兩個(gè) PageView 的子類。
解決這個(gè)問題的方法是可以使用參數(shù)化的工廠方法敦腔,可以給工廠方法傳遞一個(gè)參數(shù)(標(biāo)識(shí)符)均澳,然后根據(jù)標(biāo)識(shí)符來實(shí)例化特定的產(chǎn)品,這樣我們就不需要各種 PageView 子類了符衔。但是一旦擴(kuò)充了新產(chǎn)品(增加了新的XXXLabel或者XXXButton)找前,就得去修改相應(yīng)的工廠方法以支持新產(chǎn)品。這時(shí)候如果是支持范型的語言判族,就可以使用范型參數(shù)來解決這個(gè)問題(前提是工廠方法中沒有針對(duì)某個(gè)特定子類產(chǎn)品的操作)躺盛,我們把 PageView 改成一個(gè)范型類:
class PageView<L: UILabel, B: UIButton>: UIView {
init() {
super.init(frame: UIScreen.mainScreen().bounds)
let label = createLabel()
let button = createButton()
addSubview(label)
addSubview(button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createLabel() -> L {
return L()
}
func createButton() -> B {
return B()
}
}
這樣就可以在 Controller 中指定要返回那種類型的 PageView,可以任意組合 Label 和Button:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let pageView = PageView<HelloLabel, HelloButton>()
view.addSubview(pageView)
}
}
效果如下:
若要使用 GoodPage形帮,只需要把PageView<HelloLabel, HelloButton>()
換成PageView<GoodbyeLabel, GoodbyeButton>()
槽惫,運(yùn)行效果就變成:
Prototype(原型)
原型模式顧名思義就是將某個(gè)實(shí)例對(duì)象當(dāng)做原型,通過復(fù)制它來創(chuàng)建其他同類型的對(duì)象沃缘。要使用原型模式需要給產(chǎn)品類設(shè)置一個(gè)用來克隆自身實(shí)例的函數(shù)躯枢,雖然很多語言或者標(biāo)準(zhǔn)庫都有 copy 函數(shù),對(duì)克隆對(duì)象提供了一些原生支持槐臀,但你還得考慮深拷貝和淺拷貝的問題锄蹂,前者同時(shí)拷貝對(duì)象內(nèi)部的狀態(tài),后者則通過指針共享狀態(tài)水慨。
像 Self得糜、JavaScript 這樣基于原型的語言可以說處處都用到了原型模式敬扛,而像SmallTalk、OC朝抖、Ruby 等動(dòng)態(tài)語言中啥箭,類本身可以當(dāng)作對(duì)象傳遞并用其創(chuàng)建實(shí)例對(duì)象,甚至在 Swift 中也可以直接用所謂的元類型(SomeClass.Type)來初始化一個(gè)對(duì)象治宣,所以我覺得這個(gè)原型模式在很多時(shí)候并不實(shí)用急侥。它最大的優(yōu)點(diǎn)是靈活性,可以動(dòng)態(tài)指定要?jiǎng)?chuàng)建的對(duì)象侮邀,而這點(diǎn)坏怪,可以通過傳遞“元類型”或者利用范型輕易做到。
Abstract Factory(抽象工廠)
抽象工廠通常是利用工廠方法來實(shí)現(xiàn)的绊茧,也可以利用范型或者原型铝宵。它的核心思路就是單獨(dú)抽象出一個(gè)工廠類,通過對(duì)象組合华畏,系統(tǒng)委托這個(gè)工廠類來創(chuàng)建一系列產(chǎn)品鹏秋。沒錯(cuò),這個(gè)模式的重點(diǎn)就在于強(qiáng)調(diào)了“一系列”這三個(gè)字亡笑。如果你的最終目的是要把這一系列產(chǎn)品組合成一個(gè)產(chǎn)品侣夷,那就應(yīng)該用 Builder 模式。
所以我上面舉的那個(gè)實(shí)例其實(shí)用 Builder 模式比較合適况芒,當(dāng)然惜纸,我這里也可以強(qiáng)行用抽象工廠做一下,只要把最后組裝產(chǎn)品那一步留到外部好了绝骚。
//MARK: - 抽象工廠
protocol UIFactory {
func createLabel() -> UILabel
func createButton() -> UIButton
}
//MARK: - 具體工廠
class HelloUIFactory: UIFactory {
func createLabel() -> UILabel {
return HelloLabel()
}
func createButton() -> UIButton {
return HelloButton()
}
}
class GoodbyeUIFactory: UIFactory {
func createLabel() -> UILabel {
return GoodbyeLabel()
}
func createButton() -> UIButton {
return GoodbyeButton()
}
}
然后修改 PageView,構(gòu)造器以一個(gè) UIFactory 對(duì)象為參數(shù):
class pageView: UIView {
init(factory: UIFactory) {
super.init(frame: UIScreen.mainScreen().bounds)
let label = factory.createLabel()
let button = factory.createButton()
addSubview(label)
addSubview(button)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
如果要生成一個(gè) HelloPaveView 并顯示祠够,只需要在 Controller 的 ViewDidLoad 方法中這樣寫就好了:
let pageView = PageView(factory: HelloUIFactory())
view.addSubview(pageView)
當(dāng)然跟工廠方法一樣压汪,為了避免類繼承層次過深,也可以使用范型版本的工廠古瓤,只不過這樣在使用的時(shí)候就需要明確指出需要?jiǎng)?chuàng)建的產(chǎn)品類型了:
//用范型控制產(chǎn)品類型止剖,不需要定義一堆 UIFactory 子類。
class GenericUIFactory<L: UILabel, B: UIButton>: UIFactory {
func createLabel() -> UILabel {
return L()
}
func createButton() -> UIButton {
return B()
}
}
使用:
let pageView = PageView(factory: GenericUIFactory<HelloLabel, HelloButton>())
Builder(生成器)
生成器模式同樣需要用到對(duì)象組合落君,director 對(duì)象委托 builder 對(duì)象一步步構(gòu)建出一個(gè)復(fù)雜對(duì)象穿香。先定義一個(gè) Director 類:
class Director {
func createPageViewWithBuilder(builder: Builder) {
builder.createButton()
builder.createLabel()
}
}
Builder 定義如下:
protocol Builder {
func createLabel()
func createButton()
}
注意這個(gè) Builder 的協(xié)議并沒有聲明返回產(chǎn)品的方法,因?yàn)橛?Builder 構(gòu)造出來的產(chǎn)品往往差別很大绎速,并沒有一個(gè)統(tǒng)一的接口皮获,所以只需要在子類中聲明一個(gè)返回特定產(chǎn)品的方法即可(當(dāng)然本例中其實(shí)最終構(gòu)造的產(chǎn)品都是UIView,是可以提供一個(gè)統(tǒng)一接口的)纹冤。下面是 Builder 子類實(shí)現(xiàn):
class HelloPageBuilder: Builder {
var helloPageView: UIView
init() {
helloPageView = UIView()
}
func createLabel() {
helloPageView.addSubview(HelloLabel())
}
func createButton() {
helloPageView.addSubview(HelloButton())
}
func getHelloPageView() -> UIView {
return helloPageView
}
}
class GoodbyePageBuilder: Builder {
var goodbyePageView: UIView
init() {
goodbyePageView = UIView()
}
func createLabel() {
goodbyePageView.addSubview(GoodbyeLabel())
}
func createButton() {
goodbyePageView.addSubview(GoodbyeButton())
}
func getGoodbyePageView() -> UIView {
return goodbyePageView
}
}
使用:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let director = Director()
let builder = HelloPageBuilder()
director.createPageViewWithBuilder(builder)
let pageView = builder.getHelloPageView()
view.addSubview(pageView)
}
}
上述的 Builder 也用了工廠方法來實(shí)現(xiàn)洒宝,同樣你可以根據(jù)實(shí)際情況使用范型或者原型模式來實(shí)現(xiàn)购公。
Singleton(單例)
單例實(shí)在是太簡單了,無非就是確保只實(shí)例化某個(gè)類一次雁歌。在某些語言中宏浩,使用單例時(shí)得注意線程安全。但在 Swift 中靠瞎,只要使用let
聲明一個(gè)常量比庄,用它指向一個(gè)實(shí)例,它的 immutable 性質(zhì)可以保證線程安全乏盐,然后把對(duì)應(yīng)的構(gòu)造器設(shè)為 private 就可以了佳窑,像這樣:
private let instance = HelloUIFactory()
class HelloUIFactory: UIFactory {
private init() {}
class func singleInstance() -> HelloUIFactory {
return instance
}
func createLabel() -> UILabel {
return HelloLabel()
}
func createButton() -> UIButton {
return HelloButton()
}
}
有一點(diǎn)需要注意,Swift 的private
關(guān)鍵字的作用域是以文件為單位的丑勤,而不是類华嘹,所以雖然把HelloUiFactory
類的構(gòu)造器聲明為private
了,但在本文件內(nèi)(哪怕在HelloUiFactory
類外部)法竞,還是可以實(shí)例化該類耙厚。
使用單例:
let factory = HelloUIFactory.singleInstance()
一般像 factory 啊 builder 啊 prototype 啊等等其實(shí)都只要一個(gè)實(shí)例就夠了,所以你喜歡的話很多地方都可以用單例模式岔霸。
后記
創(chuàng)建型模式就介紹到這里薛躬,接下來會(huì)再寫一篇結(jié)構(gòu)型模式介紹和一篇行為型模式介紹〈粝福可能你覺得很多模式平常根本用不到型宝,沒關(guān)系,理解并就行了絮爷。等哪天你看別人的源碼的時(shí)候看著看著福至心靈:“耶趴酣?這不是XXX模式么?”坑夯,或者等你的項(xiàng)目規(guī)模大到一定程度的時(shí)候腦中靈光一閃:“這里用XXX模式似乎不錯(cuò)~”的時(shí)候岖寞,你就知道模式有什么用了。當(dāng)然柜蜈,時(shí)代在發(fā)展仗谆,很多現(xiàn)代語言或者標(biāo)準(zhǔn)庫已經(jīng)集成了一些模式,不需要自己費(fèi)力去實(shí)現(xiàn)了淑履。還有很多場景隶垮,哪怕可以使用模式也需要進(jìn)行一定的變通,不要照搬照抄秘噪、強(qiáng)行套用狸吞。