23個經典設計模式的Swift實現

darrenzheng發表於2018-03-15

前言

這是一篇主觀的文章,文字解釋也儘可能簡單,寫作目的是一次團隊內的知識分享,想讓不瞭解設計模式的同事迅速對這些生詞混個臉熟。所以本文適合懂Swift語法,想快速瞭解23個設計模式大概在講什麼的同學。

前置知識 UML那些線

基本結構

  • 比喻 讓我聯想到的一些事物
  • 官方定義 原版定義
  • UML 不是原版UML 只保留了我覺得核心的部分
  • 程式碼 Swift實現,這個是本體
  • 講解 假設已經看過程式碼的一些零散評註

目錄

  • Creational 建立型 5
    • Abstract Factory 抽象工廠模式
    • Builder 建造者模式
    • Factory Method 工廠方法模式
    • Prototype 原型模式
    • Singleton 單例模式
  • Structural 結構型 7
    • Adapter 介面卡模式
    • Bridge 橋接模式
    • Composite 組合模式
    • Decorator 裝飾者模式
    • Facade 外觀模式
    • Flyweight 享元模式
    • Proxy 代理模式
  • Behavioral 行為型 11
    • Chain of responsibility 責任鏈模式
    • Command 命令模式
    • Interpreter 直譯器模式
    • Iterator 迭代器模式
    • Mediator 中介模式
    • Memento 備忘錄模式
    • Observer 觀察者模式
    • State 狀態模式
    • Strategy 策略模式
    • Template Method 模板方法模式
    • Visitor 訪問者模式

工廠模式

工廠模式顧名思義,就像一個工廠生產你所需要的產品

23個經典設計模式的Swift實現
配圖:http://cdn1.alphr.com/sites/alphr/files/2016/02/tesla_factory_tour_1.jpg

無工廠 Non-Factory

也就是工廠問題想解決的原始問題。

23個經典設計模式的Swift實現

protocol Product {}
class ConcreteProductA: Product {}
class ConcreteProductB: Product {}

class Client {
    func createProduct(type: Int) -> Product {
        if type == 0 {
            return ConcreteProductA()
        } else {
            return ConcreteProductB()
        }
    }
}

let c = Client()
c.createProduct(type: 0) // get ConcreteProductA
複製程式碼

從程式碼和UML可以看出,為了得到產品A,呼叫者Client要同時依賴Product, ConcreteProductAConcreteProductB,並親自寫一個建立產品的方法。

每當需求新增一個產品,就要改動到呼叫方Client。如果這一堆建立程式碼如果可以抽離出去就好了,於是簡單工廠出現了。

簡單工廠 Simple Factory

簡單工廠就做了一件事,把Client要做的建立工作,挪到了另一個類裡

23個經典設計模式的Swift實現

protocol Product {}
class ConcreteProductA: Product {}
class ConcreteProductB: Product {}

class Client {
    let s = Factory()
}

class Factory {
    func createProduct(type: Int) -> Product {
        if type == 0 {
            return ConcreteProductA()
        } else {
            return ConcreteProductB()
        }
    }
}

let c = Client()
c.s.createProduct(type: 0) // get ConcreteProductA
複製程式碼

Factory代替了Client對具體Product的依賴,那麼當需求變化的時候,我們不再需要改動呼叫方。這固然有所進步,但無法避免的是,每次變動都要在createProduct的方法內部新增一個if-else分支,這顯然違背了開閉原則

為了解決這個問題,我們引入另一個模式。

工廠方法 Factory Method

官方定義

定義一個建立物件的介面,讓其子類自己決定例項化哪一個工廠類,工廠模式使其建立過程延遲到子類進行。

23個經典設計模式的Swift實現

protocol Product {}
class ConcreteProductA: Product {}
class ConcreteProductB: Product {}

class Client {
    let f = Factory()
}

class Factory {
    func createProduct() -> Product? { return nil } // 用於繼承
    func createProduct(type: Int) -> Product? { // 用於呼叫
        if type == 0 {
            return ConcreteFactoryA().createProduct()
        } else {
            return ConcreteFactoryB().createProduct()
        }
    }
}

class ConcreteFactoryA: Factory {
    override func createProduct() -> Product? {
        // ... 產品加工過程
        return ConcreteProductA()
    }
}

class ConcreteFactoryB: Factory {
    override func createProduct() -> Product? {
        // ... 產品加工過程
        return ConcreteProductB()
    }
}

let c = Client()
c.f.createProduct(type: 0) // get ConcreteProductA
複製程式碼

對於工廠方法的實現,有眾多不同的解法,比如Factory只保留一個createProduct讓子類實現,讓Client來選擇生成哪個具體工廠例項;或是引入一個FactoryMaker的中間層,作為生產工廠的“簡單工廠”。我這裡採用的方式是Factory既作為工廠父類,讓具體工廠決定生產生麼產品,又作為介面類,讓Client可以通過依賴注入選擇特定工廠。我這樣做的目的是,在不引入新的中間層的情況下,最小化Client的依賴。

工廠方法在簡單工廠的基礎上做了兩件事:

  • 多了一層抽象,把生產產品的工作延遲到子類執行。
  • 把“選擇如何生產產品的工作”轉化為“選擇讓哪個具體工廠生產”。

工廠方法的貢獻在於,這樣做雖然不能完美避免對一個if-else的擴充套件,但是這個擴充套件規模被極大限制住了(只需要new一個類)。

工廠方法著重點是解決了單一產品線的派生問題。那如果有多個相關產品線呢?

抽象工廠 Abstract Factory

官方定義

提供一個建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類。

23個經典設計模式的Swift實現

protocol ProductA {}
class ConcreteProductA1: ProductA {}
class ConcreteProductA2: ProductA {}

protocol ProductB {}
class ConcreteProductB1: ProductB {}
class ConcreteProductB2: ProductB {}

class Client {
    let f = Factory()
}

class Factory {
    func createProductA() -> ProductA? { return nil } // 用於繼承
    func createProductB() -> ProductB? { return nil } // 用於繼承
    func createProductA(type: Int) -> ProductA? { // 用於呼叫
        if type == 0 {
            return ConcreteFactory1().createProductA()
        } else {
            return ConcreteFactory2().createProductA()
        }
    }
    func createProductB(type: Int) -> ProductB? { // 用於呼叫
        if type == 0 {
            return ConcreteFactory1().createProductB()
        } else {
            return ConcreteFactory2().createProductB()
        }
    }
}

class ConcreteFactory1: Factory {
    override func createProductA() -> ProductA? {
        // ... 產品加工過程
        return ConcreteProductA1()
    }
    override func createProductB() -> ProductB? {
        // ... 產品加工過程
        return ConcreteProductB1()
    }
}

class ConcreteFactory2: Factory {
    override func createProductA() -> ProductA? {
        // ... 產品加工過程
        return ConcreteProductA2()
    }
    override func createProductB() -> ProductB? {
        // ... 產品加工過程
        return ConcreteProductB2()
    }
}

let c = Client()
c.f.createProductA(type: 0) // get ConcreteProductA1
c.f.createProductA(type: 1) // get ConcreteProductA2
c.f.createProductB(type: 0) // get ConcreteProductB1
c.f.createProductB(type: 1) // get ConcreteProductB2
複製程式碼

圖很嚇人,其實很簡單。

當我們有兩個相關的產品線ProductAProductB, 例如螺絲和螺母,他們派生出ProductA1ProductB1ProductA2ProductB2,前者我們由工廠ConcreteFactory1來製作,後者由 ConcreteFactory2來製作。

對於Client來說,他只需要知道有一個抽象的工廠能同時生產ProductAProductB就行了,那就是圖中的Factory。

重點來了,這個抽象的Factory是通過“工廠方法”模式把構造過程延遲到子類執行的,也就是說,抽象工廠是建立在工廠方法的基礎上的模式。

所以抽象工廠,換句話說,就是多個產品線需要繫結在一起,形成一個抽象的綜合工廠,由具體的綜合工廠來批量實現“工廠方法”的一種更“高階”的模式。

總結

有點繞,說完這些感覺我已經中文十級了。總之,我想表達的觀點是:這些工廠模式並不是割裂的存在,而是一個遞進的思想


Builder 建造者模式

建造者模式就像你委託一個室內設計師裝修你的新家

23個經典設計模式的Swift實現
配圖: http://tse2.mm.bing.net/th?id=OIP.N3hIhcOq32Bh6Ezi6q6kGwHaFj&pid=Api

官方定義

將一個複雜的構建與其表示相分離,使得同樣的構建過程可以建立不同的表示。

如果說之前的談到的工廠模式是把建立產品(物件)的工作抽離出去的話,這次要聊的建造者模式,就是把產品內部的元件生產工作抽離出去。這樣做的場景,適用於那些有著複雜、規則模組的物件生成流程。換句話說,工廠模式是一個類(工廠)建立另一個類(產品),而建造者是一個類(產品)自身的屬性(元件)構造過程。

對於建造者模式的實現網上也是版本不一:複雜點的版本會引入一個Director的角色,做一個整體上下文,組裝更傻瓜化的builder和product。或是抽象一層Builder協議,用不同的具體Builder來構造不同的產品。但我認為這些都模糊了這個模式要傳達的焦點,對理解沒有幫助,所以這裡我選擇一個極簡的模型。

23個經典設計模式的Swift實現

struct Builder {
    var partA: String
    var partB: String
}

struct Product {
    var partA: String
    var partB: String
    init(builder: Builder) {
        partA = builder.partA
        partB = builder.partB
    }
}

// 通過builder完成產品建立工作
let b = Builder(partA: "A", partB: "B")
// 這樣產品只需要一個builder就可以完成製作
let p = Product(builder: b)
複製程式碼

我們讓Product的生成由自己發起,但是它的元件(屬性)全都委託給Builder來實現,而它只需要依賴一個Builder就完成了自身的生產工作。


Prototype 原型模式

原型模式讓你有了一個可以源源不斷自我賦值的類。

23個經典設計模式的Swift實現
配圖: http://thumbs.dreamstime.com/z/cell-division-two-cells-divide-osmosis-background-other-cells-48181492.jpg

官方定義

用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。

原型模式很簡單,你只要實現一個返回你自己的新物件的方法即可。這裡我採用的實現還不是最簡單的,這個interface並不是必須的。

原型模式實現了深拷貝。

23個經典設計模式的Swift實現

protocol Prototype {
    func clone() -> Prototype
}

struct Product: Prototype {
    var title: String
    func clone() -> Prototype {
        return Product(title: title)
    }
}

let p1 = Product(title: "p1")
let p2 = p1.clone()
(p2 as? Product)?.title // OUTPUT: p1
複製程式碼

Singleton 單例模式

單例就像一個公司的IT部門,他們是唯一的存在,並且被所有人直接訪問。

23個經典設計模式的Swift實現
配圖:The IT Crowd

官方定義

保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

因為第二點常常被忽視,所以過度使用的危害極大,你無從知道呼叫從何而來,這種goto一般的存在會變成維護的噩夢。

單例比較常見的應用是例如資料庫,網路框架的全域性訪問點。

單例其實就是變種的原型模式,只不過原型每次返回的是一個拷貝。

23個經典設計模式的Swift實現

Swift的簡單實現:

class Singleton {
    static let sharedInstance = Singleton()
    private init() {
        // 用private防止被new
    }
}
let s  = Singleton.sharedInstance
let s2 = Singleton() // ERROR: initializer is inaccessible due to 'private' protection level
複製程式碼

Swift的完整實現:

class Singleton {
    static var singleton: Singleton? = nil
    private init() {}
    static func sharedInstance() -> Singleton {
        if singleton == nil {
            singleton = Singleton()
        }
        return singleton!
    }
}

let s = Singleton.sharedInstance()
let s2 = Singleton() // ERROR: initializer is inaccessible due to 'private' protection level
複製程式碼

Adapter 介面卡模式

介面卡就像一個電源轉換插頭。

23個經典設計模式的Swift實現
配圖: http://www.wap135.com/270/timg08/uploaded/i8/TB2452gtpXXXXaLXXXXXXXXXXXX_!!622114048.jpg

官方定義

將一個類的介面轉換成客戶希望的另外一個介面。介面卡模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作。

一個類能稱之為介面卡,就是指它能把另一個類進行某種變形,讓其能和實際需求對接。 比如一個底層資料模型,可以通過不同的UI介面卡,對應不同的展現需要。

23個經典設計模式的Swift實現

protocol Target {
    var value: String { get }
}

struct Adapter: Target {
    let adaptee: Adaptee
    var value: String {
        return "\(adaptee.value)"
    }
    init(_ adaptee: Adaptee) {
        self.adaptee = adaptee
    }
}

struct Adaptee {
    var value: Int
}

Adapter(Adaptee(value: 1)).value // "1"
複製程式碼

Bridge 橋接模式

橋接模式就是這麼一座橋,它矗立在具體和抽象之間,當你呼叫的時候只看到了抽象,但是它內部實現時“橋接”到了具體。

23個經典設計模式的Swift實現
配圖: https://refactoring.guru/design-patterns/bridge

官方定義

將抽象部分與實現部分分離,使它們都可以獨立的變化。

橋接模式是繼工廠模式之後另一個有點繞的概念,為了不一言不合就拋概念把大家繞暈,我們直接來看一下這個模式最終成型的樣子。

23個經典設計模式的Swift實現

首先假設我們封裝了一個“開關能力”的介面。

protocol 開關能力 {
    func turnOn(_ on: Bool)
}
複製程式碼

這個介面只有一個方法,實現者需要提供開啟/關閉開關會觸發什麼操作。

然後我們抽象一個叫“裝置”的類出來,這個類就有意思了:

  • 首先他有一個實現了開關能力的例項變數,並通過初始化賦好值。
  • 他有一個方法的實現就是直接提供了開關能力裡的實現。
class 裝置 {
    let obj: 開關能力
    func turnOn(_ on: Bool) {
        obj.turnOn(on)
    }
    init(_ obj: 開關能力) {
        self.obj = obj
    }
}
複製程式碼

這樣,其實一個橋接模式就已經搭建完了。再講解之前,我們直接看一下如何應用它:

class 電視: 開關能力 {
    func turnOn(_ on: Bool) {
        if on {
            // 開啟電視
        } else {
            // 關閉電視
        }
    }
}

class 空調: 開關能力 {
    func turnOn(_ on: Bool) {
        if on {
            // 開啟空調
        } else {
            // 關閉空調
        }
    }
}

let tv = 裝置(電視())
tv.turnOn(true) // 開啟電視

let aircon = 裝置(空調())
aircon.turnOn(false) // 關閉空調
複製程式碼

通過這段程式碼可以看出:

  • 在把抽象的裝置應用到具體的業務的時候,這個模式採用的是組合了一個實現了開關能力介面的例項,而沒用繼承。
  • 最終呼叫的時候,是由統一的裝置作為接入點的,而不是電視空調這些具體類,具體的實現是通過組合的方式注入裝置裡的。

瞭解了這個流程後,一個事情就明朗了:

這不就是在用組合代替繼承嗎?

沒錯,它把需要變化的程式碼通過介面代理出去,而避免了繼承。

但是,橋接模式的在哪裡?

這時,要搬出他的概念了: Bridge pattern - Wikipedia

The bridge pattern is a design pattern used in software engineering which is meant to "decouple an abstraction from its implementation so that the two can vary independently". 橋接模式解耦了抽象和具體,讓它們可以獨立變化。

從程式碼去尋找具體和抽象,就可以發現:

23個經典設計模式的Swift實現

這座橋能走通的關鍵就是這個組合在抽象類裡的變數。

最後,你會發現,這不就是Adapter和Adaptee嗎? 沒錯,設計模式其實是連續劇來著。

不同在於介面卡的關注點是如何讓兩個不相容的類對接, 而橋接模式關注點是解耦


Composite 組合模式

組合模式就像一個公司的組織架構,存在基層員工(Leaf)和管理者(Composite),他們都實現了元件(Component)的work方法,這種樹狀結構的每一級都是一個功能完備的個體。

23個經典設計模式的Swift實現
配圖:https://realtimeboard.com/static/images/page/examples/detail/organizational-chart.png

官方定義

將物件組合成樹形結構以表示"部分-整體"的層次結構。組合模式使得使用者對單個物件和組合物件的使用具有一致性。

這個組合模式不是“組合優於繼承”的那種“組合”,這裡是狹義的指代一種特定場景,就是如配圖描述的一種樹狀結構。

理解這個模式要知道三個設定:

  • Component:一個有功能性的元件。
  • Leaf:它實現了元件。
  • Composite:它既實現了元件,他又包含多個元件。

23個經典設計模式的Swift實現

protocol Component {
    func someMethod()
}

class Leaf: Component {
    func someMethod() {
        // Leaf
    }
}

class Composite: Component {
    var components = [Component]()
    func someMethod() {
        // Composite
    }
}

let leaf = Leaf()
let composite = Composite()
composite.components += [leaf]

composite.someMethod()
leaf.someMethod()
複製程式碼

這個模式的精髓就是Composite這個角色,事實上Leaf可以看做一個特殊的Compostie。由於他即可以是一個功能執行者,又可以包含其它節點,這個特性可以派生出泛用度很高的樹狀結構。


Decorator 裝飾者模式

如果點咖啡時價格的計算是牛奶(糖(咖啡(價格: 19元)))就好了

23個經典設計模式的Swift實現
配圖:http://cdn.marksdailyapple.com/wordpress/wp-content/themes/Marks-Daily-Apple-Responsive/images/blog2/coffee.jpg

官方定義

動態地給一個物件新增一些額外的職責。就增加功能來說,裝飾器模式相比生成子類更為靈活。

下面我們就用裝飾者模式的思想來設計這個點咖啡程式:

23個經典設計模式的Swift實現

程式碼

protocol Component {
    var cost: Int { get }
}

protocol Decorator: Component {
    var component: Component { get }
    init(_ component: Component)
}

struct Coffee: Component {
    var cost: Int
}

struct Sugar: Decorator {
    var cost: Int {
        return component.cost + 1
    }
    var component: Component
    init(_ component: Component) {
        self.component = component
    }
}

struct Milk: Decorator {
    var cost: Int {
        return component.cost + 2
    }
    var component: Component
    init(_ component: Component) {
        self.component = component
    }
}

Milk(Sugar(Coffee(cost: 19))).cost
複製程式碼

當你的需求是零散的不斷給“主菜加點佐料”的時候,並且這些佐料會經常變化,那麼這個模式就可以有效的解決排列組合產生的類爆炸

理解的一個關鍵點就是區分元件Component裝飾者Decorator兩個角色,單純元件的實現者(咖啡)是被裝飾的物件,他不再能裝飾別人。

這個模式沒有一個集中的計算器,每一個裝飾者都參與了部分計算並輸出當下的結果。


Facade 外觀模式

外觀模式就是化繁為簡。

23個經典設計模式的Swift實現
配圖:http://www.3dmgame.com/news/201709/3684484.html

官方定義

為子系統中的一組介面提供一個一致的介面,外觀模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。

值得一提的是,這個單詞讀音很怪。

23個經典設計模式的Swift實現

在談起外觀模式的時候,常常是指對一個複雜的(舊)程式碼庫在不改變其內在的情況下,包裝一層易於呼叫的表層API。

外觀模式不會採用繼承,而是用介面和組合。

23個經典設計模式的Swift實現

protocol Facade {
    func simpleMethod()
}

class LegacyCode {
    func someMethod1() { }
    func someMethod2() { }
}

extension LegacyCode: Facade {
    func simpleMethod() {
        someMethod1()
        someMethod2()
    }
}

class Client {
    let f: Facade = LegacyCode()
}

let c = Client()
c.f.simpleMethod()
複製程式碼

Flyweight 享元模式

享元模式就像CPU的Cache Memory,它通過對快取的命中來實現速度的提升和記憶體的降低。

23個經典設計模式的Swift實現
配圖:http://www.computerhope.com/issues/pictures/cpu_cache_die.jpg

官方定義

運用共享技術有效地支援大量細粒度的物件。

享元模式其實就是指一套快取系統。 顯然他是一種複合模式,使用工廠模式來創造例項。 適用場景是系統中存在重複的物件建立過程。 好處是節省了記憶體加快了速度。

23個經典設計模式的Swift實現

struct TargetObject {
    var title: String?
    func printTitle() {
        print(title)
    }
}

class Cache {
    var targetObjects = [String: TargetObject]()
    func lookup(key: String) -> TargetObject {
        if targetObjects.index(forKey: key) == nil {
            return TargetObject()
        }
        return targetObjects[key]!
    }
}

let c = Cache()
c.targetObjects["Test"] = TargetObject(title: "Test")
c.lookup(key: "123").printTitle() // nil
c.lookup(key: "Test").printTitle() // Test
複製程式碼

Proxy 代理模式

代理模式讓一個類成為了另一個類的實際介面。

23個經典設計模式的Swift實現
配圖:http://tse2.mm.bing.net/th?id=OIP.igizh16RdxH-Xq9jCR7KLgHaCx&w=300&h=112&c=7&o=5&dpr=2&pid=1.7

官方定義

為其他物件提供一種代理以控制對這個物件的訪問。

有兩種常見的代理場景:

  • Protection proxy: 出於安全考慮,通過一個表層的類間接呼叫底層的類。
  • Virtual proxy:出於效能考慮,通過一個低耗的類延遲呼叫一個高耗的類。

但他們的實現是類似的:

23個經典設計模式的Swift實現

protocol Subject {
    mutating func operation()
}

struct SecretObject: Subject {
    func operation() {
        // real implementation
    }
}

struct PublicObject: Subject {
    private lazy var s = SecretObject()
    mutating func operation() {
        s.operation()
    }
}

var p = PublicObject()
p.operation()
複製程式碼

SecretObject既可以看做一個隱藏類,又可以看做一個高費的類。通過PublicObject對他的代理,可以實現資訊隱藏和延遲載入兩個特性。


Chain of responsibility 責任鏈模式

責任鏈模式就像去取錢,每一面值都參與了計算和職責傳遞。

23個經典設計模式的Swift實現
配圖:http://www.joezimjs.com/wp-content/uploads/chain_of_responsibility_atm.png

官方定義

避免請求傳送者與接收者耦合在一起,讓多個物件都有可能接收請求,將這些物件連線成一條鏈,並且沿著這條鏈傳遞請求,直到有物件處理它為止。

UIKit的touch事件就應用了責任鏈模式

這個模式的實現就是既實現一個介面,又組合這個介面,這樣自己執行完畢後就可以呼叫下一個執行者。

23個經典設計模式的Swift實現

protocol ChainTouchable {
    var next: ChainTouchable? { get }
    func touch()
}

class ViewA: ChainTouchable {
    var next: ChainTouchable? = ViewB()
    func touch() {
        next?.touch()
    }
}

class ViewB: ChainTouchable {
    var next: ChainTouchable? = ViewC()
    func touch() {
        next?.touch()
    }
}

class ViewC: ChainTouchable {
    var next: ChainTouchable? = nil
    func touch() {
        print("C")
    }
}

let a = ViewA()
a.touch() // OUTPUT: C
複製程式碼

Command 命令模式

命令模式就是一種指令系統。

23個經典設計模式的Swift實現
配圖:http://cdn.wikimg.net/strategywiki/images/thumb/e/e3/Light-bot_2-3.jpg/914px-Light-bot_2-3.jpg

官方定義

將一個請求封裝成一個物件,從而使您可以用不同的請求對客戶進行引數化。

命令模式有兩個特徵:

  • 命令邏輯物件化,這個命令物件可以從外界通過引數傳入,也可內部實現。
  • 支援undo,因為每個命令物件自己知道如何撤銷(反命令),所以可以封裝到命令物件內部。

23個經典設計模式的Swift實現


protocol Command {
    var operation: () -> Void { get }
    var backup: String { get }
    func undo()
}

struct ConcreteCommand: Command {
    var backup: String
    var operation: () -> Void
    func undo() {
        print(backup)
    }
}

struct Invoker {
    var command: Command
    func execute() {
        command.operation()
    }
    func undo() {
        command.undo()
    }
}

let printA = ConcreteCommand(backup: "Default A") {
    print("A")
}
let i1 = Invoker(command: printA)
i1.execute() // OUTPUT: A

let printB = ConcreteCommand(backup: "Default B") {
    print("B")
}
let i2 = Invoker(command: printB)
i2.execute() // OUTPUT: B
i2.undo() // OUTPUT: Default B
複製程式碼

Interpreter 直譯器模式

有了直譯器,Skyrim的龍語也不在話下。

23個經典設計模式的Swift實現
配圖:https://staticdelivery.nexusmods.com/mods/110/images/thumbnails/32821-3-1362476170.jpg

官方定義

給定一個語言,定義它的文法表示,並定義一個直譯器,這個直譯器使用該標識來解釋語言中的句子。

23個經典設計模式的Swift實現

我們可以實現一個能計算文字“1加1”的直譯器。

protocol Expression {
    func evaluate(_ context: String) -> Int
}

struct MyAdditionExpression: Expression {
    func evaluate(_ context: String) -> Int {
        return context.components(separatedBy: "加")
            .flatMap { Int($0) }
            .reduce(0, +)
    }
}

let c = MyAdditionExpression()
c.evaluate("1加1") // OUTPUT: 2
複製程式碼

Iterator 迭代器模式

迭代器就像回轉壽司,保證每一道菜品都能得到展示。

23個經典設計模式的Swift實現
配圖:http://www.sohu.com/a/207894434_99978064

官方定義

提供一種方法順序訪問一個聚合物件中各個元素, 而又無須暴露該物件的內部表示。

迭代器就是能用hasNext和Next來遍歷的一種集合元素。

他很像責任鏈,但是責任鏈是一個隨時可斷的鏈條(有可能在某一個節點不再把責任下放),他不強調一次完整的遍歷。迭代器更像一次次的迴圈,每次迴圈都強調完整性,所以更適合集合的場景。

還有一個區別是迭代器是提供元素,而責任鏈在每一個經手人那做業務。

23個經典設計模式的Swift實現

protocol AbstractIterator {
    func hasNext() -> Bool
    func next() -> Int?
}

class ConcreteIterator: AbstractIterator {
    private var currentIndex = 0
    var elements = [Int]()
    

    func next() -> Int? {
        guard currentIndex < elements.count else { currentIndex = 0; return nil }
        defer { currentIndex += 1 }
        return elements[currentIndex]
    }
    
    func hasNext() -> Bool {
        guard currentIndex < elements.count else { currentIndex = 0; return false }
        return elements[currentIndex] != nil
    }
}

protocol AbstractCollection {
    func makeIterator() -> AbstractIterator
}

class ConcreteCollection: AbstractCollection {
    let iterator = ConcreteIterator()
    func add(_ e: Int) {
        iterator.elements.append(e)
    }
    func makeIterator() -> AbstractIterator {
        return iterator
    }
}

let c = ConcreteCollection()
c.add(1)
c.add(2)
c.add(3)

let iterator = c.makeIterator()
while iterator.hasNext() {
    print(iterator.next() as Any)
}
複製程式碼

Mediator 中介模式

中介模式是物件間發訊息的傳話筒。

23個經典設計模式的Swift實現
配圖:http://www.quanjing.com/imgbuy/top-959496.html

官方定義

用一箇中介物件來封裝一系列的物件互動,中介者使各物件不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。

中介模式對於通訊關係複雜的系統有很好的解耦效果

它和觀察者模式很像,區別在於觀察者是不關心接受方的廣播,中介者是介入兩個(或多個)物件之間的定點訊息傳遞。

23個經典設計模式的Swift實現

protocol Receiver {
    func receive(message: String)
}

protocol Mediator: class {
    func notify(message: String)
    func addReceiver(_ receiver: Receiver)
}

class ConcreteMediator: Mediator {
    var recipients = [Receiver]()
    func notify(message: String) {
        recipients.forEach { $0.receive(message: message) }
    }
    func addReceiver(_ receiver: Receiver) {
        recipients.append(receiver)
    }
}

protocol Component: Receiver {
    var mediator: Mediator? { get }
}

struct ConcreteComponent: Component {
    weak var mediator: Mediator?
    var name: String
    func receive(message: String) {
        print(name, " receive: ", message)
    }
}

var mediator = ConcreteMediator()

let c1 = ConcreteComponent(mediator: mediator, name: "c1")
let c2 = ConcreteComponent(mediator: mediator, name: "c2")
let c3 = ConcreteComponent(mediator: mediator, name: "c3")

mediator.addReceiver(c1)
mediator.addReceiver(c2)
mediator.addReceiver(c3)

//c1  receive:  hi
//c2  receive:  hi
//c3  receive:  hi
c1.mediator?.notify(message: "hi")
複製程式碼

Memento 備忘錄模式

備忘錄就是遊戲存檔。

23個經典設計模式的Swift實現
配圖:http://gearnuke.com/wp-content/uploads/2015/05/witcher3-ps4-savedata-2.jpg

官方定義

在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。

這個模式有兩個角色,一個是要儲存的型別本身(Memento)和執行儲存操作的保管人(Caretaker)。

23個經典設計模式的Swift實現

typealias Memento = [String: String] // chatper: level
protocol MementoConvertible {
    var memento: Memento { get }
    init?(memento: Memento)
}

class GameState: MementoConvertible {
    var memento: Memento {
        return [chapter: level]
    }
    var chapter: String
    var level: String
    
    required init?(memento: Memento) {
        self.chapter = memento.keys.first ?? ""
        self.level = memento.values.first ?? ""
    }
    init(chapter: String, level: String) {
        self.chapter = chapter
        self.level = level
    }
}

protocol CaretakerConvertible {
    static func save(memonto: Memento, for key: String)
    static func loadMemonto(for key: String) -> Memento?
}

class Caretaker: CaretakerConvertible {
    static func save(memonto: Memento, for key: String) {
        let defaults = UserDefaults.standard
        defaults.set(memonto, forKey: key)
        defaults.synchronize()
    }
    
    static func loadMemonto(for key: String) -> Memento? {
        let defaults = UserDefaults.standard
        return defaults.object(forKey: key) as? Memento
    }

}

let g = GameState(chapter: "Prologue", level: "0")
// after a while
g.chapter = "Second"
g.level = "20"
// want a break
Caretaker.save(memonto: g.memento, for: "gamename")
// load game
let gameState = Caretaker.loadMemonto(for: "gamename") // ["Second": "20"]
複製程式碼

Observer 觀察者模式

觀察者模式就像預購,支付了就坐等收貨,省心省力。

23個經典設計模式的Swift實現
配圖:http://images.pushsquare.com/news/2015/01/what_the_hecks_going_on_with_playstation_store_pre-orders_in_europe/attachment/0/original.jpg

官方定義

定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。

觀察者模式定義了物件之間的一對多依賴,這樣一來,當一個物件改變狀態時,它的所有依賴者都會收到通知並自動更新。

觀察者模式適用在一個被觀察者(資料來源)要通知多個觀察者的場景。

這個模式主要通過新增一層介面,來把觀察者的具體型別擦除,從何實現鬆耦合。(針對介面程式設計,而非實現)

觀察者模式一個最重要的特點就是它是一種推模型(PUSH),在很多情況下比拉模型更高效和即時。

23個經典設計模式的Swift實現

protocol Observable {
    var observers: [Observer] { get }
    func add(observer: Observer)
    func remove(observer: Observer)
    func notifyObservers()
}

class ConcreteObservable: Observable {
    var observers = [Observer]()
    func add(observer: Observer) {
        observers.append(observer)
    }
    func remove(observer: Observer) {
        if let index = observers.index(where: { $0 === observer }) {
            observers.remove(at: index)
        }
    }
    func notifyObservers() {
        observers.forEach { $0.update() }
    }
}

protocol Observer: class {
    func update()
}

class ConcreteObserverA: Observer {
    func update() { print("A") }
}

class ConcreteObserverB: Observer {
    func update() { print("B") }
}

//////////////////////////////////

let observable = ConcreteObservable()
let a = ConcreteObserverA()
let b = ConcreteObserverB()
observable.add(observer: a)
observable.add(observer: b)
observable.notifyObservers() // output: A B

observable.remove(observer: b)
observable.notifyObservers() // output: A
複製程式碼

State 狀態模式

狀態模式就像馬里奧吃了各種蘑菇後的變身。

23個經典設計模式的Swift實現
配圖:http://www.superluigibros.com/images/super_mario_bros_3_power_ups.jpg

官方定義

允許物件在內部狀態發生改變時改變它的行為,物件看起來好像修改了它的類。

狀態模式很像策略模式,但是他封裝的不是演算法,而是一個一個本體下的不同狀態。

23個經典設計模式的Swift實現

protocol State {
    func operation()
}

class ConcreteStateA: State {
    func operation() {
        print("A")
    }
}

class ConcreteStateB: State {
    func operation() {
        print("B")
    }
}

class Context {
    var state: State = ConcreteStateA()
    func someMethod() {
        state.operation()
    }
}

let c = Context()
c.someMethod() // OUTPUT: A
c.state = ConcreteStateB() // switch state
c.someMethod() // OUTPUT: B
複製程式碼

Strategy 策略模式

策略模式就像不同的交通路線,可以相互替換,都能達到終點。

23個經典設計模式的Swift實現
配圖:高德地圖iOS

官方定義

定義一系列的演算法,把它們一個個封裝起來, 並且使它們可相互替換。

這個模式主要還是在解決一個穩定的繼承樹當遇到需求變化時的狼狽。他把變化的幾種情況分門別類的封裝好,然後把自己變化的部分隔離成一個介面例項(interface/protocol),讓子類繼承後來做選擇題。

一個需要轉變的思維就是:建模一個類的時候不能只侷限於物體(object),也可以是行為(behavior)的封裝。

用繼承來替換interface實現多型的behavior也是理論可行的。 這個模式還有一個強大之處:他是動態的。就是至於子類選擇了什麼行為,不見得是寫程式碼的時候寫死的;也可以是類似使用者點選一下切換了演算法。

23個經典設計模式的Swift實現


protocol WeaponBehavior {
    func use()
}

class SwordBehavior: WeaponBehavior {
    func use() { print("sword") }
}

class BowBehavior: WeaponBehavior {
    func use() { print("bow") }
}

class Character {
    var weapon: WeaponBehavior?
    func attack() {  weapon?.use() }
}

class Knight: Character {
    override init() {
        super.init()
        weapon = SwordBehavior()
    }
}

class Archer: Character {
    override init() {
        super.init()
        weapon = BowBehavior()
    }
}

///////////////////////////////////

Knight().attack() // output: sword
Archer().attack() // output: bow
複製程式碼

Template Method 模板方法模式

模板方法就是抽象類衍生出來的繼承樹。

23個經典設計模式的Swift實現
配圖:https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3167143621,3882552175&fm=27&gp=0.jpg

官方定義

定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。

模板方法指的就是存在在抽象類中的方法宣告,由子類具體實現。這樣這套方法產生了一套模具不同產品的派生效果。

通過抽象類和子類實現多型。

很可惜Swift/Objective-C都沒有抽象類, 所以用一般的class來實現

23個經典設計模式的Swift實現

class Soldier {
    func attack() {} // <-- Template Method
    private init() {} // <-- avoid creation
}

class Paladin: Soldier {
    override func attack() {
        print("hammer")
    }
}

class Archer: Soldier {
    override func attack() {
        print("bow")
    }
}
複製程式碼

Visitor 訪問者模式

訪問者模式就像一個個酒店,他們都有接待你的介面,你入住後又會利用它的基礎設施。

23個經典設計模式的Swift實現
配圖:https://marketingland.com/wp-content/ml-loads/2014/08/hotel-bell-customer-service-ss-1920.jpg

官方定義

主要將資料結構與資料操作分離。

號稱最複雜的設計模式,先看一下實現程式碼。

23個經典設計模式的Swift實現

protocol Visitor {
    func visit(_ c: ComponentA)
    func visit(_ c: ComponentB)
}

struct ConcreteVisitor: Visitor {
    func visit(_ c: ComponentA) {
        c.featureA()
    }
    func visit(_ c: ComponentB) {
        c.featureB()
    }
}

protocol Component {
    func accept(_ v: Visitor)
}

struct ComponentA: Component {
    func featureA() {
        print("Feature A")
    }
    func accept(_ v: Visitor) {
        v.visit(self)
    }
}

struct ComponentB: Component {
    func featureB() {
        print("Feature B")
    }
    func accept(_ v: Visitor) {
        v.visit(self)
    }
}

let components: [Component] = [ComponentA(), ComponentB()]
components.forEach {
    $0.accept(ConcreteVisitor())
}
複製程式碼

要理解這個模式需要了解一些概念:

通過這篇文章可以瞭解到, 其實設計模式(GoF)中的Visitor模式就是Java解決Double Dispatch的一種應用。

我們用Swift實現一下文中雙重派發問題

protocol Event {}
class BlueEvent: Event {}
class RedEvent: Event {}

class Handler {
    func handle(_ e: Event) {
        print("Event")
    }
    func handle(_ r: RedEvent) {
        print("RedEvent")
    }
    func handle(_ b: BlueEvent) {
        print("BlueEvent")
    }
}

let b = BlueEvent()
let r = RedEvent()
let c = Handler()

c.handle(b) // OUTPUT: BlueEvent
c.handle(r) // OUTPUT: RedEvent
複製程式碼

驗證發現,Swift是支援雙重派發的。

那什麼是雙重派發(Double Dispatch)

要解釋這個,要先搞清楚什麼是派發(Dispatch)

派發或者說Single Dispatch,Dynamic Dispatch,wiki是這樣解釋的:

In most object-oriented systems, the concrete function that is called from a function call in the code depends on the dynamic type of a single object and therefore they are known as single dispatch calls, or simply virtual function calls. 抓要點說,就是多型的情況下,在執行時,虛方法是由哪個具體實現執行了,這樣的繫結動作就是派發。

那什麼虛方法? 看一下wiki的解釋:

In short, a virtual function defines a target function to be executed, but the target might not be known at compile time. 虛方法就是編譯期還沒確定實現的方法,我們可以理解成介面中宣告的方法。

好,我們現在知道了一次虛方法的繫結就是派發,雙重派發就是兩次繫結操作。

那雙重派發的程式碼是如何觸發派發的呢?

  • overload 過載 過載產生的多型引起一次派發
  • override 重寫 介面實現的多型引起第二次派發

理解這些就知道這個模式在折騰什麼了。

當然他也有另外的好處,就是可以讓具體的業務類可以丟到集合裡批量的執行accept visitor。

相關文章