iOS設計模式詳解

在軟件工程中,(引自維基百科)設計模式(design pattern)是對軟件設計中廣泛存在(反覆出現)的各類問題,所提出的解決方案。這個術語是由埃裏希·伽瑪(Erich Gamma)等人在1990年代從建築設計領域引入到計算機科學的。 設計模式並不直接用來完成代碼的編寫,而是描述在各類不一樣狀況下,要怎麼解決問題的一種方案。面向對象設計模式一般以類別或對象來描述其中的關係和相互做用,但不涉及用來完成應用程序的特定類別或對象。設計模式能使不穩定依賴於相對穩定、具體依賴於相對抽象,避免會引發麻煩的緊耦合,以加強軟件設計面對並適應變化的能力。html

使用設計模式的目的:爲了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設計模式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構同樣。算法

講到設計模式,必然要提設計原則,一樣爲了實現代碼複用,要遵循軟件設計原則。設計模式就是實現了這些原則,達到了代碼的複用、可維護的目的。下面從7種設計原則,23種設計模式,來說解軟件編寫過程當中用到的這些知識點shell

設計原則

編寫 全稱 中文
S Single Responsibility Principle 單一職責原則
O Open Close Principle 開閉原則
L Liskov Substitution Principle 里氏替換原則
I Interface Segregation Principle 接口隔離原則
D Dependence Inversion Principle 依賴倒置原則
L Law Of Demeter 迪米特法則
C Composite/Aggregate Reuse Principle CARP 組合/聚合複用原則

前面五種被稱爲面向對象設計中經常使用的SOLID原則。數據庫

單一職責原則

理解:不一樣的類具有不一樣的職責,各司其職。作系統設計是,若是發現有一個類擁有了兩種職責,那麼就要問一個問題:能夠將這個類分紅兩個類嗎?若是真的有必要,那就分開,千萬不要讓一個類乾的事情太多。編程

總結:一個類只承擔一個職責設計模式

例子:咱們設計一個訂單列表,列表分爲待支付、待收貨、已收貨等列表,那咱們是寫一個類,使用if判斷是哪一個類型,而後請求相應的數據,仍是寫多個類,分別執行各自的功能呢。不少人會覺的寫一個類比較省事,可是過多的判斷條件,各類職責冗餘到一個類中真的好嗎,若是待支付列表須要加一些特殊的功能呢,待收貨也須要加一些功能呢,那這個類是否是變得條件判斷異常的多。因此仍是寫成多個類,實現各自的邏輯比較好。其實另外咱們寫列表的Cell,也是一個道理,分紅幾種類型的Cell去寫,而不是一個Cell實現幾種類型。數組

import Foundation

class OrderList: NSObject {//訂單列表
    var waitPayList: WaitPayList?//待支付
    var waitGoodsList: WaitGoodsList?//待收貨
    var receivedGoodsList: ReceivedGoodsList?//已收貨
}

class WaitPayList: NSObject {
    
}

class WaitGoodsList: NSObject {
    
}

class ReceivedGoodsList: NSObject {
    
}
複製代碼

開閉原則

理解:類、模塊、函數,能夠去擴展,但不要去修改。若是要修改代碼,儘可能用繼承或組合的方式來擴展類的功能,而不是直接修改類的代碼。固然,若是能保證對整個架構不會產生任何影響,那就不必搞的那麼複雜,直接改這個類吧。安全

總結:對軟件實體的改動,最好用擴展而非修改的方式。bash

例子:咱們設計支付功能的時候,會用到不一樣的支付方式,咱們能夠選擇在支付的時候使用判斷支付條件而後使用不一樣的支付方式,然而這種設計真的好嗎。若是咱們添加了一個支付方法或者刪除了一個支付方法是否是要改動pay方法的邏輯,那每一次的調整都要改動pay方法的邏輯是否是不合理了,依據開閉原則具體作法應該是設計擴展支付方式來實現不一樣的支付。微信

修改以前代碼

import Foundation

class PayHelper {
    func pay(send: PaySendModel) -> Void {
        if send.type == 0 {
            //支付寶支付
        }
        else if send.type == 1 {
            //微信支付
        }
    }
}

class PaySendModel {
    var type: Int = 0
    var info: [String: AnyHashable]?
}
複製代碼

修改以後

import Foundation

class PayHelper {
    var processors: [Int: PayProcessor]?
    
    func pay(send: PaySendModel) -> Void {
        guard let processors = processors else {return}
        guard let payProcessor: PayProcessor = processors[send.type] else {return}
        
        payProcessor.handle(send: send)//支付
    }
}

class PaySendModel {
    var type: Int = 0
    var info: [String: AnyHashable]?
}

protocol PayProcessor {
    func handle(send: PaySendModel)
}

class AliPayProcessor: PayProcessor {
    func handle(send: PaySendModel) {
        
    }
}

class WeChatPayProcessor: PayProcessor {
    func handle(send: PaySendModel) {
        
    }
}
複製代碼

能夠看到修改以後的支付,擴展起來是否是很方便,增長支付方式只須要繼承PayProcessor就好了,不須要更改pay方法了。

里氏替換原則

理解:一個對象在其出現的任何地方,均可以用子類實例作替換,而且不會致使程序的錯誤。換句話說,當子類能夠在任意地方替換基類且軟件功能不受影響時,這種繼承關係的建模纔是合理的。

總結:子類能夠擴展父類的方法,但不該該複寫父類的方法。

例子:咱們定義汽車的基類,基類裏面有行駛的方法,如今咱們有個寶馬車,寶馬車繼承汽車基類,也有行駛方法。如今咱們想知道寶馬車的行駛速度是多少,該怎麼設計呢。

修改以前

import Foundation

class Car {
    func run() {
        print("汽車跑起來了")
    }
}

class BaoMaCar: Car {
    override func run() {
        super.run()
        
        print("當前行駛速度是80Km/h")
    }
}
複製代碼

能夠看到咱們重寫了run方法,增長了汽車行駛速度的邏輯,這樣是不知足的里氏替換原則的。由於全部基類Car替換成子類BaoMaCar,run方法的行爲跟之前不是如出一轍了。

修改以後

import Foundation

class Car {
    func run() {
        print("汽車跑起來了")
    }
}

class BaoMaCar: Car {
    func showSpeed() {
        print("當前行駛速度是80Km/h")
    }
}
複製代碼

接口隔離原則

理解:一個類實現的接口中,包含了它不須要的方法。將接口拆分紅更小和更具體的接口,有助於解耦,從而更容易重構、更改。

總結:對象不該被強迫依賴它不使用的方法。

例子:咱們定義一個汽車接口,要求實現run等方法。

修改以前

import Foundation

protocol ICar {
    func run()
    func showSpeed()
    func playMusic()
}

class Car: ICar {
    func run() {
        print("汽車跑起來了")
    }
    
    func showSpeed() {
        print("當前行駛速度是80Km/h")
    }
    
    func playMusic() {
        print("播放音樂")
    }
}
複製代碼

能夠看到咱們定義Car實現了ICar的接口,可是並非每一個車都有播放音樂的功能的,這樣對於通常的低端車沒有這個功能,對於他們來講,這個接口的設計就是冗餘的。

修改以後

import Foundation

protocol IProfessionalCar {//具有通常功能的車
    func run()
    func showSpeed()
}

protocol IEntertainingCar {//具有娛樂功能的車
    func run()
    func showSpeed()
    func playMusic()
}

class SangTaNaCar: IProfessionalCar {//桑塔納轎車
    func run() {
        print("汽車跑起來了")
    }
    
    func showSpeed() {
        print("當前行駛速度是80Km/h")
    }
}

class BaoMaCar: IEntertainingCar {//寶馬轎車
    func run() {
        print("汽車跑起來了")
    }
    
    func showSpeed() {
        print("當前行駛速度是80Km/h")
    }
    
    func playMusic() {
        print("播放音樂")
    }
}
複製代碼

接口隔離原則的要求咱們,創建單一接口,不要創建龐大臃腫的接口,儘可能細化接口,接口中的方法儘可能少。這經過分散定義多個接口,能夠預防外來變動的擴散,提升系統的靈活性和可維護性。

依賴倒置原則

理解:高層模塊不該該依賴低層模塊,兩者都應該依賴其抽象;抽象不該該依賴細節;細節應該依賴抽象。

總結:面向接口編程,提取出事務的本質和共性。

例子:咱們給汽車加油,實現可以加90號的汽油,若是沒有,就加93號的汽油。

修改以前

import Foundation

class Car {
    func refuel(_ gaso: Gasoline90) {
        print("加90號汽油")
    }
    
    func refuel(_ gaso: Gasoline93) {
        print("加93號汽油")
    }
}

class Gasoline90 {
    
}

class Gasoline93 {
    
}
複製代碼

上面這段代碼有什麼問題,能夠看到Car高層模塊依賴了底層模塊Gasoline90和Gasoline93,這樣寫是不符合依賴倒置原則的。

修改以後

import Foundation

class Car {
    func refuel(_ gaso: IGasoline) {
        print("加\(gaso.name)汽油")
    }
}

protocol IGasoline {
    var name: String { get }
}

class Gasoline90: IGasoline {
    var name: String = "90號"
}

class Gasoline93: IGasoline {
    var name: String = "93號"
}
複製代碼

修改以後咱們高層模塊Car依賴了抽象IGasoline,底層模塊Gasoline90和Gasoline93也依賴了抽象IGasoline,這種設計是符合依賴倒置原則的。

迪米特法則

理解:一個對象對另外一個對象瞭解得越多,那麼,它們之間的耦合性也就越強,當修改其中一個對象時,對另外一個對象形成的影響也就越大。

總結:一個對象應該對其餘對象保持最少的瞭解,實現低耦合、高內聚。

例子:實現一個給汽車加油的設計,使的咱們能夠隨時保證加油的質量過關。

修改以前

import Foundation

class Person {
    var car: Car?
    
    func refuel(_ gaso: IGasoline) {
        if gaso.isQuality == true {//若是汽油質量過關,咱們就給汽車加油
            car?.refuel(gaso)
        }
    }
}

class Car {
    func refuel(_ gaso: IGasoline) {
        print("加\(gaso.name)汽油")
    }
}

protocol IGasoline {
    var name: String { get }
    var isQuality: Bool { get }
}

class Gasoline90: IGasoline {
    var name: String = "90號"
    var isQuality: Bool = false
}

class Gasoline93: IGasoline {
    var name: String = "93號"
    var isQuality: Bool = true
}
複製代碼

能夠看到上面有個問題,咱們怎麼知道汽油的質量是否過關呢,即時咱們知道,加油判斷油的質量這個事情也不該該由咱們來作。

修改以後

import Foundation

class Person {//給車加油的人
    var car: Car?
    
    func refuel(_ worker: WorkerInPetrolStation, _ gaso: IGasoline) {
        guard let car = car else {return}
        
        worker.refuel(car, gaso)
    }
}

class WorkerInPetrolStation {//加油站工做人員
    func refuel(_ car: Car, _ gaso: IGasoline) {
        if gaso.isQuality == true {//若是汽油質量過關,咱們就給汽車加油
            car.refuel(gaso)
        }
    }
}

class Car {
    func refuel(_ gaso: IGasoline) {
        print("加\(gaso.name)汽油")
    }
}

protocol IGasoline {
    var name: String { get }
    var isQuality: Bool { get }
}

class Gasoline90: IGasoline {
    var name: String = "90號"
    var isQuality: Bool = false
}

class Gasoline93: IGasoline {
    var name: String = "93號"
    var isQuality: Bool = true
}
複製代碼

能夠看到這樣咱們就實現了低耦合,咱們只須要知道有加油站工做人員和要加的汽油就好了,不須要知道太多汽油相關的知識,以及加油相關的操做流程,這些都交給了工做人員,這樣是符合咱們的迪米特原則的。

組合/聚合複用原則

理解:合成/聚合複用原則就是在一個新的對象裏面使用一些已有的對象,使之成爲新對象的一部分;新的對象經過向這些對象的委派達到複用已有功能的目的。它的設計原則是:要儘可能使用合成/聚合,儘可能不要使用繼承。

總結:就是說要少用繼承,多用合成關係來實現。

繼承複用有必定的缺點:好比若是基類發生了改變,那麼派生類的的實現就不得不發生改變;並且從超類繼承而來的實現是靜態的,不可能在運行時發生改變,所以它的靈活性差並最終會限制複用性。

使用組合/聚合複用原則就解決了繼承複用的缺點。

例子:咱們實現一個角色,能夠是僱員、經理等。

人被繼承到僱員,學生,經理子類。而實際上,僱員、學生和經理分別描述一種角色,而人能夠同時有幾種不一樣的角色。好比,一我的既然是經理了就必定是僱員,使用繼承來實現角色,則只能使用每個人具備一種角色,這顯然是不合理的。錯誤的緣由就是把角色的等級結構和人的等級結構混淆起來,把Has-A的關係誤認爲是Is-A的關係,經過下面的改正就能夠正確的作到這一點。

從上圖能夠看出,每個人均可以有一個以上的角色,因此一我的能夠同時是僱員又是經理。從這個例子能夠看出,當一個類是另外一個類的角色時,不該該使用繼承描述這種關係。

有了以上設計原則,咱們能夠遵循設計原則實現不少好的設計模式,下面講講設計模式。

建立型模式

在軟件工程中,引自維基百科建立型模式是處理對象建立的設計模式,試圖根據實際狀況使用合適的方式建立對象。基本的對象建立方式可能會致使設計上的問題,或增長設計的複雜度。建立型模式經過以某種方式控制對象的建立來解決問題。 建立型模式由兩個主導思想構成。一是將系統使用的具體類封裝起來,二是隱藏這些具體類的實例建立和結合的方式。

主要用於建立對象。

抽象工廠模式

提供一個接口,用於建立與某些對象相關或依賴於某些對象的類家族,而又不須要指定它們的具體類。經過這種模式能夠去除客戶代碼和來自工廠的具體對象細節之間的耦合關係。

類簇是一種把一個公共的抽象超類下的一些私有的具體子類組合在一塊兒的架構。抽象超類負責聲明建立私有子類實例的方法,會根據被調用方法的不一樣分配恰當的具體子類,每一個返回的對象均可能屬於不一樣的私有子類。

Cocoa將類簇限制在數據存儲可能因環境而變的對象生成上。Foundation框架爲NSStringNSDataNSDictionaryNSSet、和NSArray對象定義了類簇。公共超類包括上述的不可變類和與其相互補充的可變類NSMutableStringNSMutableDataNSMutableDictionaryNSMutableSet、和NSMutableArray

import Foundation

class GzCity {//廣州市有兩個啤酒廠
    var abstractFactory1: IAbstractFactory?
    var abstractFactory2: IAbstractFactory?
}

protocol IAbstractFactory {//抽象工廠
    func createProductA() -> IProduct
    func createProductB() -> IProduct
}

protocol IProduct {
    var name: String { get }
}

class BearProduct: IProduct {//啤酒產品
    var name: String = "啤酒"
}

class ConcreteFactory1: IAbstractFactory {//啤酒工廠1
    func createProductA() -> IProduct {
        return BearProduct()
    }
    
    func createProductB() -> IProduct {
        return BearProduct()
    }
}

class ConcreteFactory2: IAbstractFactory {//啤酒工廠2
    func createProductA() -> IProduct {
        return BearProduct()
    }
    
    func createProductB() -> IProduct {
        return BearProduct()
    }
}
複製代碼

建造者模式

將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。

  • 當建立複雜對象的算法應該獨立於該對象的組成部分以及它們的裝配方式時。

  • 當構造過程必須容許被構造的對象有不一樣的表示時。

import Foundation

protocol IBuilder {
    func createProduct() -> IProduct
}

protocol IProduct {
    var name: String { get }
}

class BeerProduct: IProduct {
    var name: String = "啤酒"
}

class Director {//領導
    var builder: ConcreteBuilder?
    
    func construct() {//指導生產
        guard let product = builder?.createProduct() else {return}
        print("生產產品" + product.name)
    }
}

class ConcreteBuilder: IBuilder {//生產者
    func createProduct() -> IProduct {
        return BeerProduct()
    }
}
複製代碼

工廠方法模式

定義一個用於建立對象的接口,讓子類決定實例化哪個類。Factory Method 使一個類的實例化延遲到其子類。

  • 當一個類不知道它所必須建立的對象的類的時候。

  • 當一個類但願由它的子類來指定它所建立的對象的時候。

  • 當類將建立對象的職責委託給多個幫助子類中的某一個,而且你但願將哪個幫助子類是代理者這一信息局部化的時候。

import Foundation

class ConcreteCreator: ICreator {//生產者
    func factoryMethod() -> IProduct {
        return ConcreteProduct()
    }
}

protocol ICreator {
    func factoryMethod() -> IProduct
}

protocol IProduct {
    var name: String { get }
}

class ConcreteProduct: IProduct {
    var name: String = "啤酒"
}
複製代碼

原型模式

用原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象。

原型模式是很是簡單的一種設計模式, 在多數狀況下可被理解爲一種深複製的行爲。在Objective-C中使用原型模式, 首先要遵循NSCoping協議(OC中一些內置類遵循該協議, 例如NSArray, NSMutableArray等)。剛纔咱們提到了深複製, 一圖以蔽之:

下面爲UML圖

import Foundation

class Client {
    var prototype: IPrototype!
    
    func operation() -> IProduct {
        return prototype.clone()
    }
}

protocol IPrototype {
    func clone() -> IProduct
}

protocol IProduct {
    var name: String { get }
}

class ConcreteProduct: IProduct, IPrototype {
    var name: String = "啤酒"
    
    func clone() -> IProduct {
        let p = ConcreteProduct()
        p.name = name
        return p
    }
}
複製代碼

單例模式

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。該類須要跟蹤單一的實例,並確保沒有其它實例被建立。單例類適合於須要經過單個對象訪問全局資源的場合。

有幾個Cocoa框架類採用單例模式,包括NSFileManagerNSWorkspace、和NSApplication類。這些類在一個進程中只能有一個實例。當客戶代碼向該類請求一個實例時,獲得的是一個共享的實例,該實例在首次請求的時候被建立。

import Foundation

class Singleton {
    static let instance: Singleton = Singleton()
    
    init() {
        
    }
}
複製代碼

結構型模式

在軟件工程中結構型模式是設計模式,藉由一以貫之的方式來了解元件間的關係,以簡化設計。

主要用於處理類或對象的組合。

適配器模式

將一個類的接口轉換成另一個客戶但願的接口。Adapter 模式使得本來因爲接口不兼容而不能一塊兒工做的那些類能夠一塊兒工做。

咱們可使用適配器模式,把UITableView的接口進行二次封裝,統一對外回調咱們關心的接口。好比點擊cell的事件回調等。

import Foundation

class ListAdaper<T>: UITableViewDelegate, UITableViewDataSource {
    
    var cellClick: ((_ obj: T) -> Void)?
    
    init(_ tableView: UITableView) {
        tableView.delegate = self
    }
    
    ...
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        cellClick?(datas[indexPath.row])
    }
    
}
複製代碼

能夠看到以上代碼,大大簡化了接口的複雜度,適配回調給咱們的接口是咱們關心和使用的到的就好了。節約了不少的代碼成本,增長了維護性。

橋接模式

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

  • Abstraction是一個虛基類,Operation()調用Implemetor的OperationImp()方法
  • Implemetor父類有兩個子類A和B。
  • 兩個基類間的關係是聚合關係,Abstrction是總體,Implementor是部分,說白了就是Implementor是Abstraction的成員變量。

好了,下面用橋接模式來實現下面這個比較簡單的場景: 花園裏有一朵牽牛花和一朵牡丹花,牽牛花開會吸引蜜蜂來採蜜,牡丹花開會吸引蝴蝶來採蜜。

import Foundation

protocol IAbstractInsect {
    func bloomImp()
}

protocol IAbstractFlower {
    var insect: IAbstractInsect? { get }
    
    func bloom()
}

class QianniuHua: IAbstractFlower {
    var insect: IAbstractInsect?
    
    func bloom() {
        print("牽牛花開了")
        insect?.bloomImp()
    }
}

class MudanHua: IAbstractFlower {
    var insect: IAbstractInsect?
    
    func bloom() {
        print("牡丹花開了")
        insect?.bloomImp()
    }
}

class Butterfly: IAbstractInsect {
    func bloomImp() {
        print("蝴蝶來了")
    }
}

class Bee: IAbstractInsect {
    func bloomImp() {
        print("蜜蜂來了")
    }
}

let qianniu = QianniuHua.init()
qianniu.insect = Bee.init()
qianniu.bloom()

let mudan = MudanHua.init()
mudan.insect = Butterfly.init()
mudan.bloom()
複製代碼

打印以下: 牽牛花開了 蜜蜂來了 牡丹花開了 蝴蝶來了

組合模式

這種模式將互相關聯的對象合成爲樹結構,以表現部分-所有的層次結構。它使客戶代碼能夠統一地處理單獨的對象和多個對象的合成結果。

合成對象是模型-視圖-控制器彙集模式的一部分。

組合模式最大的優勢是他的節點能夠自由增長,且調用節點方便。

import UIKit

class Composite: NSObject {
    var subComposites: NSMutableArray = {NSMutableArray()}()
    var parentComposite: Composite?
    
    func addComposite(comp: Composite) {
        subComposites.add(comp)
        comp.parentComposite = self
    }

    func removeCompositeAtIndex(index:Int)  {
        subComposites.remove(at: index)
    }

    func removeComposite(comp: Composite)  {
        subComposites.remove(comp)
    }

    func removeFromParent()  {
        if (self.parentComposite != nil) {
            self.parentComposite?.removeComposite(comp: self)
        }
    }
}
複製代碼

裝飾模式

這種模式動態地將額外的責任附加到一個對象上。在進行功能擴展時,裝飾是子類化以外的一種靈活的備選方法。和子類化同樣,採納裝飾模式能夠加入新的行爲,而又沒必要修改已有的代碼。裝飾將須要擴展的類的對象進行包裝,實現與該對象相同的接口,並在將任務傳遞給被包裝對象以前或以後加入本身的行爲。裝飾模式表達了這樣的設計原則:類應該接納擴展,但避免修改。

裝飾是用於對象合成的模式,在您本身的代碼中應該鼓勵使用對象的合成。然而,Cocoa本身提供了一些基於這種模式的類和機制。在這些實現中,擴展對象並不徹底複製它所包裝的對象的接口,雖然具體實現中可使用不一樣的技術來進行接口共享。

Cocoa在實現某些類時用到了裝飾模式,包括NSAttributedStringNSScrollView、和NSTableView。後面兩個類是複合視圖的例子,它們將其它一些視圖類的對象組合在一塊兒,而後協調它們之間的交互。

動態地給一個對象添加一些額外的職責。就增長功能來講,Decorator模式相比生成子類更爲靈活。

經過類別實現裝飾模式,若是你是一名iOS開發者,你可能當即會想到用類別來實現。沒錯,用類別實現能夠達到一樣的效果,並且會更簡單。類別是Objective-C的特性,它能夠添加類的行爲,而不用進行子類化,經過類別添加的方法不會影響類原來的方法,類別也成爲類的一部分,並可由其子類繼承。

  雖然經過類別能夠實現裝飾模式,可是這並非一種嚴格的實現,由類別添加的方法是編譯時綁定的,而裝飾模式是動態綁定的,另外類別也沒有封裝被擴展類的實例。類別適合裝飾器很少的時候,上面的例子只有一個NetData裝飾器,用類別實現會更輕量,更容易。

外觀模式

這種模式爲子系統中的一組接口提供統一的接口。表觀模式定義一個更高級別的接口,經過減小複雜度和隱藏子系統之間的通信和依賴性,使子系統更加易於使用。

NSImage類爲裝載和使用基於位圖(好比JPEG、PNG、或者TIFF格式)或向量(EPS或PDF格式)的圖像提供統一的接口。NSImage能夠爲同一個圖像保持多個表示,不一樣的表示對應於不一樣類型的NSImageRep對象。NSImage能夠自動選擇適合於特定數據類型和顯示設備的表示。同時,它隱藏了圖像操做和選擇的細節,使客戶代碼能夠交替使用不少不一樣的表示。

  • 實現了子系統與客戶端之間的鬆耦合關係。
  • 客戶端屏蔽了子系統組件,減小了客戶端所需處理的對象數目,並使得子系統使用起來更加容易。

在生活中不少地方也用到外觀模式,好比購買基金,咱們從基金機構那裏購買基金,而後他們幫咱們管理咱們的基金,去操做和運行,咱們只管購買和賣出就好了,而不用去管他們內部的操做,下面是UML圖和具體實現:

/// 基金類
class Fund {
    var gu1 = Stock1()
    var gu2 = Stock2()
    var gu3 = Stock3()
    var nd  = NationalDebt()
    var rt  = Realty()

    public func buyFund() {
        print("買入基金")
        gu1.buy()
        gu2.buy()
        gu3.buy()
        nd.buy()
        rt.buy()
    }

    public func shellFund() {
        print("\n賣出基金")
        gu1.shell()
        gu2.shell()
        gu3.shell()
        nd.shell()
        rt.shell()
    }
}

//股票類
class Stock1: Deal {
    var dealName: String {
        return "股票一"
    }
}

class Stock2: Deal {
    var dealName: String {
        return "股票二"
    }
}

class Stock3: Deal {
    var dealName: String {
        return "股票三"
    }
}

class NationalDebt: Deal {
    var dealName: String {
        return "國債"
    }
}

class Realty: Deal {
    var dealName: String {
        return "房地產"
    }
}

protocol Deal {
    var dealName: String {get}
    mutating func shell()
    mutating func buy()
}

extension Deal {
    mutating func shell() {
        print("\(dealName)賣出")
    }

    mutating func buy() {
        print("\(dealName)買入")
    }
}

let jijin = Fund()
// 基金購買
jijin.buyFund()
// 基金贖回
jijin.shellFund()
複製代碼

打印以下: 買入基金 股票一買入 股票二買入 股票三買入 國債買入 房地產買入

賣出基金 股票一賣出 股票二賣出 股票三賣出 國債賣出 房地產賣出

以上就是簡單的外觀模式的實現,定義高層接口,基金的買入和賣出,去使用整個基金系統,而不用去管內部是怎麼操做的。

享元模式

運用共享技術有效地支持大量細粒度的對象。

適用性

  • 一個應用程序使用了大量的對象。

  • 徹底因爲使用大量的對象,形成很大的存儲開銷。

  • 對象的大多數狀態均可變爲外部狀態。

  • 若是刪除對象的外部狀態,那麼能夠用相對較少的共享對象取代不少組對象。

  • 應用程序不依賴於對象標識。因爲Flyweight對象能夠被共享,對於概念上明顯有別的對象,標識測試將返回真值。

例子:咱們在一個界面上生成1000個花,共有5種花,實現方式以下。

修改前

- (void)viewDidLoad
{
    [super viewDidLoad];

//使用普通模式
    for (int i = 0; i < 100000; i++) {
        @autoreleasepool {
            CGRect screenBounds = [[UIScreen mainScreen] bounds];
            CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
            CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
            NSInteger minSize = 10;
            NSInteger maxSize = 50;
            CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
            CGRect area = CGRectMake(x, y, size, size);

            FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
            //新建對象
            UIImageView *imageview = [self flowerViewWithType:flowerType];
            imageview.frame = area;
            [self.view addSubview:imageview];
        }
    }
}

- (UIImageView *)flowerViewWithType:(FlowerType)type
{
    UIImageView *flowerView = nil;
    UIImage *flowerImage;

    switch (type)
    {
        case kAnemone:
            flowerImage = [UIImage imageNamed:@"anemone.png"];
            break;
        case kCosmos:
            flowerImage = [UIImage imageNamed:@"cosmos.png"];
            break;
        case kGerberas:
            flowerImage = [UIImage imageNamed:@"gerberas.png"];
            break;
        case kHollyhock:
            flowerImage = [UIImage imageNamed:@"hollyhock.png"];
            break;
        case kJasmine:
            flowerImage = [UIImage imageNamed:@"jasmine.png"];
            break;
        case kZinnia:
            flowerImage = [UIImage imageNamed:@"zinnia.png"];
            break;
        default:
            break;
    }

    flowerView = [[UIImageView alloc]initWithImage:flowerImage];

    return flowerView;
}
複製代碼

上面的代碼能夠看到每次要新建一個flowerView添加到self.view視圖上,這樣作的後果是什麼呢,特別是生成大量的對象的時候。

能夠看到內存暫用很大,代碼上看,其實只有6種花放在不一樣的位置而已,那咱們能夠利用享元模式的思想,複用這6種花,而後繪製到不一樣位置,而不是增長對象添加到視圖上。

修改以後

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>


@interface FlowerView : UIImageView
{

}

- (void) drawRect:(CGRect)rect;

@end

==================

#import "FlowerView.h"
#import <UIKit/UIKit.h>

@implementation FlowerView

- (void) drawRect:(CGRect)rect
{
  [self.image drawInRect:rect];
}

@end
複製代碼
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef enum  
{
  kAnemone,
  kCosmos,
  kGerberas,
  kHollyhock,
  kJasmine,
  kZinnia,
  kTotalNumberOfFlowerTypes
} FlowerType;

@interface FlowerFactory : NSObject 
{
  @private
  NSMutableDictionary *flowerPool_;
}

- (UIImageView *) flowerViewWithType:(FlowerType)type;

@end

======================

#import "FlowerFactory.h"
#import "FlowerView.h"

@implementation FlowerFactory


- (UIImageView *)flowerViewWithType:(FlowerType)type
{
  if (flowerPool_ == nil)
  {
    flowerPool_ = [[NSMutableDictionary alloc] 
                   initWithCapacity:kTotalNumberOfFlowerTypes];
  }

  UIImageView *flowerView = [flowerPool_ objectForKey:[NSNumber
                                                  numberWithInt:type]];

  if (flowerView == nil)
  {
    UIImage *flowerImage;

    switch (type) 
    {
      case kAnemone:
        flowerImage = [UIImage imageNamed:@"anemone.png"];
        break;
      case kCosmos:
        flowerImage = [UIImage imageNamed:@"cosmos.png"];
        break;
      case kGerberas:
        flowerImage = [UIImage imageNamed:@"gerberas.png"];
        break;
      case kHollyhock:
        flowerImage = [UIImage imageNamed:@"hollyhock.png"];
        break;
      case kJasmine:
        flowerImage = [UIImage imageNamed:@"jasmine.png"];
        break;
      case kZinnia:
        flowerImage = [UIImage imageNamed:@"zinnia.png"];
        break;
      default:
        break;
    } 

    flowerView = [[FlowerView alloc] 
                   initWithImage:flowerImage];
    [flowerPool_ setObject:flowerView 
                    forKey:[NSNumber numberWithInt:type]];
  }

  return flowerView;
}


@end
複製代碼
#import "ViewController.h"
#import "FlowerFactory.h"
#import "FlyweightView.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

// 使用享元模式
    FlowerFactory *factory = [[FlowerFactory alloc] init];
    NSMutableArray *flowerList = [[NSMutableArray alloc]
                                   initWithCapacity:500];
    for (int i = 0; i < 10000; ++i)
    {
        @autoreleasepool {
            FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
            //重複利用對象
            UIImageView *flowerView = [factory flowerViewWithType:flowerType];

            CGRect screenBounds = [[UIScreen mainScreen] bounds];
            CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
            CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
            NSInteger minSize = 10;
            NSInteger maxSize = 50;
            CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;

            CGRect area = CGRectMake(x, y, size, size);
            //新建對象
            NSValue *key = [NSValue valueWithCGRect:area];
            //新建對象
            NSDictionary *dic =   [NSDictionary dictionaryWithObject:flowerView forKey:key];
            [flowerList addObject:dic];

        }

    }

    FlyweightView *view = [[FlyweightView alloc]initWithFrame:self.view.bounds];
    view.flowerList = flowerList;
    self.view = view;


}

@end
複製代碼
#import <UIKit/UIKit.h>

@interface FlyweightView : UIView 

@property (nonatomic, retain) NSArray *flowerList;

@end

==================

#import "FlyweightView.h"
#import "FlowerView.h"

@implementation FlyweightView

extern NSString *FlowerObjectKey, *FlowerLocationKey;


- (void)drawRect:(CGRect)rect 
{
  for (NSDictionary *dic in self.flowerList)
  {

      NSValue *key = (NSValue *)[dic allKeys][0];
      FlowerView *flowerView = (FlowerView *)[dic allValues][0];
      CGRect area = [key CGRectValue];
      [flowerView drawRect:area];
  }

}

@end
複製代碼

能夠看到內存已經降下來了,咱們只是生成了對象flowerView,可是並無add到FlyweightView上,[self.image drawInRect:rect];使用image從新繪製了一個新的位置去顯示。

代理模式

爲其餘對象提供一種代理以控制對這個對象的訪問。

這種模式爲某些對象定義接口,使其充當其它對象的代理或佔位對象,目的是進行訪問控制。這種模式能夠用於爲一個多是遠程的、建立起來開銷很大的、或者須要保證安全的對象建立表明對象,並在表明對象中爲其提供訪問控制的場合。它在結構上和裝飾模式相似,但服務於不一樣的目的;裝飾對象的目的是爲另外一個對象添加行爲,而代理對象則是進行訪問控制。iOS中大量的使用了代理模式,UITableView,UIScrollView,AppDelegate等。

圖中涉及的角色以下所示:

  • 協議:定義代理和委託的共同接口(方法)
  • 委託:根據指定的協議,委託代理去完成實現指定接口(方法)
  • 代理:根據指定的協議,實現委託須要實現的接口(方法)

看實現代碼

import Foundation

protocol IProxy {//協議
    func charge()
}

class A {//委託
    var delegate: IProxy?
    
    func askProxy() {
        delegate?.charge()
    }
}

class B: IProxy {//代理
    func charge() {
        print("A委託B充值,B實現了代理方法charge")
    }
}
複製代碼

行爲型模式

在軟件工程中, 引自維基百科行爲型模式爲設計模式的一種類型,用來識別對象之間的經常使用交流模式並加以實現。如此,可在進行這些交流活動時加強彈性。

用於描述對類或對象怎樣交互和怎樣分配職責。

職責鏈模式

使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。

Application Kit框架中包含一個稱爲響應者鏈的架構。該鏈由一系列響應者對象(就是從NSResponder繼承下來的對象)組成,事件(好比鼠標點擊)或者動做消息沿着鏈進行傳遞並(一般狀況下)最終被處理。若是給定的響應者對象不處理特定的消息,就將消息傳遞給鏈中的下一個響應者。響應者在鏈中的順序一般由視圖的層次結構來決定,從層次較低的響應者對象向層次較高的對象傳遞,頂點是管理視圖層次結構的窗口對象,窗口對象的委託對象,或者全局的應用程序對象。事件和動做消息在響應者鏈中的確切傳遞路徑是不盡相同的。一個應用程序擁有的響應者鏈可能和它擁有的窗口(甚至是局部層次結構中的視圖對象)同樣多,但每次只能有一個響應者鏈是活動的—也就是與當前活動窗口相關聯的那個響應鏈。

代碼實現以下

import Foundation

class DutyHandle : NSObject {
    /// 下一個
    var next : DutyHandle?
    
    /// 處理請求操做
    func handleRequest(str:String) {
        /// 若是能夠則直接處理
        if (self.canDealWithRequest(str: str)) {
            print(str)
        }
        else {
            /// 不然若是有下一個,則下一個進行處理判斷
            if ((next) != nil) {
                next?.handleRequest(str: str)
            }
        }
    }
    
    /// 判斷可否處理請求
    func canDealWithRequest(str:String) -> Bool {
        return false
    }
}
複製代碼

命令模式

將一個請求封裝爲一個對象,從而使你可用不一樣的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可取消的操做。請求對象將一或多個動做綁定在特定的接收者上。命令模式將發出請求的對象和接收及執行請求的對象區分開來。

調用對象

NSInvocation類的實例用於封裝Objective-C消息。一個調用對象中含有一個目標對象、一個方法選擇器、以及方法參數。您能夠動態地改變調用對象中消息的目標及其參數,一旦消息被執行,您就能夠從該對象獲得返回值。經過一個調用對象能夠屢次調用目標或參數不一樣的消息。

建立NSInvocation對象須要使用NSMethodSignature對象,該對象負責封裝與方法參數和返回值有關係的信息。NSMethodSignature對象的建立又須要用到一個方法選擇器。NSInvocation的實現還用到Objective-C運行環境的一些函數。

NSInvocation對象是分佈式、撤消管理、消息傳遞、和定時器對象編程接口的一部分。在須要去除消息發送對象和接收對象之間的耦合關係的相似場合下,您也可使用。

目標-動做

目標-動做機制使控件對象—也就是象按鍵或文本輸入框這樣的對象—能夠將消息發送給另外一個能夠對消息進行解釋並將它處理爲具體應用程序指令的對象。接收對象,或者說是目標,一般是一個定製的控制器對象。消息—也被稱爲動做消息—由一個選擇器來肯定,選擇器是一個方法的惟一運行時標識。典型狀況下,控件擁有的單元對象會對目標和動做進行封裝,以便在用戶點擊或激活控件時發送消息(菜單項也封裝了目標和動做,以便在用戶選擇時發送動做消息)。目標-動做機制之因此可以基於選擇器(而不是方法簽名),是由於Cocoa規定動做方法的簽名和選擇器名稱老是同樣的。

當您用Interface Builder構建程序的用戶界面時,能夠對控件的動做和目標進行設置。您所以可讓控件具備定製的行爲,而又沒必要爲控件自己書寫任何的代碼。動做選擇器和目標鏈接被歸檔在nib文件中,並在nib文件被解檔時復活。您也能夠經過向控件或它的單元對象發送setTarget:和setAction:消息來動態地改變目標和動做。

目標-動做機制常常用於通知定製控制器對象將數據從用戶界面傳遞給模型對象,或者將模型對象的數據顯示出來。Cocoa綁定技術則能夠避免這種用法,有關這種技術的更多信息請參見Cocoa綁定編程主題文檔。

代碼示例
import Foundation

class Command: NSObject {
    var receiver: Receiver?
    init(receiver:Receiver) {
        super.init()
        self.receiver = receiver
    }
    
    func execute() {
        
    }
    
    func undo() {
        
    }
}

class UpCommand: Command {
    override func execute() {
        receiver!.soundNumber += 1
    }
    override func undo() {
        receiver!.soundNumber -= 1
    }
}

class DownCommand: Command {
    override func execute() {
        receiver!.soundNumber -= 1
    }
    override func undo() {
        receiver!.soundNumber += 1
    }
}

class Receiver: NSObject {
    var soundNumber: Int = {0}()
}

class CommandManager: NSObject {
    var commandList: NSMutableArray = {NSMutableArray()}()
    var command: Command?
    
    func setCommand(command:Command) {
        self.command = command
    }
    
    func execute() {
        if (self.command != nil) {
            self.command!.execute()
            commandList.add(self.command!)
        }
    }
    
    func undo() {
        if self.commandList.count > 0 {
            let command = self.commandList.lastObject as! Command
            command.undo()
            self.commandList.removeLastObject()
        }
    }
}

class Client: NSObject {
    func begin() {
        let receiver = Receiver()
        receiver.soundNumber = 15
        let upCommand = UpCommand(receiver: receiver)
        let manager = CommandManager()
        manager.setCommand(command: upCommand)
        manager.execute()
    }
}
複製代碼

解釋器模式

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

代碼示例以下

import Foundation

class Explain: NSObject {
    func add(a: Double, b: Double) -> Double {
        return a + b
    }
    
    func multiply(a: Double, b: Double) -> Double {
        return a * b
    }
}
複製代碼

迭代器模式

這種模式提供一種順序訪問聚合對象(也就是一個集合)中的元素,而又沒必要暴露潛在表示的方法。迭代器模式將訪問和遍歷集合元素的責任從集合對象轉移到迭代器對象。迭代器定義一個訪問集合元素的接口,並對當前元素進行跟蹤。不一樣的迭代器能夠執行不一樣的遍歷策略。

Foundation框架中的NSEnumerator類實現了迭代器模式。NSEnumerator抽象類的私有具體子類返回的枚舉器對象能夠順序遍歷不一樣類型的集合—數組、集合、字典(值和鍵)—並將集合中的對象返回給客戶代碼。

NSDirectoryEnumerator是一個不緊密相關的類,它的實例能夠遞歸地枚舉文件系統中目錄的內容。

NSArrayNSSet、和NSDictionary這樣的集合類都包含相應的方法,能夠返回與集合的類型相適用的枚舉器。全部的枚舉器的工做方式都同樣。您能夠在循環中向枚舉器發送nextObject消息,若是該消息返回nil,而不是集合中的下一個對象,則退出循環。

實現棧的迭代以下

import Foundation

/// 堆棧迭代器
class StackIterator: NSObject {
    private lazy var stack = NSMutableArray()
    
    func push(object :Any) {
        stack.add(object)
    }
    
    func pop() -> Any {
        let object = readStackRear
        if empty() {
            stack.remove(object)
        }
        return object
    }
    
    /// 讀取棧尾
    func readStackRear() -> Any {
        if empty() {
            return NSNull()
        }
        let object = stack.lastObject
        return object!
    }
    
    func count() -> Int {
        return stack.count
    }
    
    func empty() -> Bool {
        return stack.count == 0
    }
}
複製代碼

實現隊列的迭代以下

import Foundation

/// 隊列迭代器
class QueueIterator: NSObject {
    private lazy var quene = NSMutableArray()
    
    func inQuene(object :Any) {
        quene.add(object)
    }
    
    func outQuene() -> Any {
        let object = readQueneHead()
        if empty() == false {
            quene.remove(object)
        }
        return object
    }
    
    /// 讀取隊首
    func readQueneHead()  -> Any {
        if empty() {
            return NSNull()
        }
        let object = quene.firstObject
        return object!
    }
    
    func count() -> Int {
        return quene.count
    }
    
    func empty() -> Bool {
        return quene.count == 0
    }
}
複製代碼

實現迭代器以下

import Foundation

/// 迭代器
class EnumIterator: NSObject {
    private(set) lazy var allObjects = NSArray()
    private lazy var index = 0
    
    init(allObjects : NSArray) {
        super.init()
        self.allObjects = allObjects
    }
    
    func nextObject() -> Any {
        if index >= allObjects.count {
            return NSNull()
        }
        let object = allObjects[index]
        index += 1
        return object
    }
    
}
複製代碼

中介者模式

用一箇中介對象來封裝一系列的對象交互。中介者使各對象不須要顯式地相互引用,從而使其耦合鬆散,並且能夠獨立地改變它們之間的交互。

使用中介以前以下圖

使用中介以後以下圖

中介者模式很好的處理了業務中組件化方案的強耦合的問題,咱們iOS當中組件化的實現都是基於中介者的模式的。其中的Mediator起到相當重要的做用,Mediator就是咱們封裝的組件化的框架。框架方案可參考iOS 組件化方案探索

備忘錄模式

這種模式在不破壞封裝的狀況下,捕捉和外部化對象的內部狀態,使對象在以後能夠回覆到該狀態。備忘錄模式使關鍵對象的重要狀態外部化,同時保持對象的內聚性。

在iOS經常使用的實現備忘錄模式的模塊有歸檔、序列化、CoreData等。

歸檔

歸檔將一個程序中的對象以及對象的屬性(包括屬性和關係)存儲到檔案上,使之能夠保存到文件系統中,或者在不一樣的處理器和網絡間傳遞。檔案將程序的對象圖保存爲獨立於架構的字節流,對象的標識和對象之間的關係都會被保留。因爲對象的類型和它的數據一塊兒被存儲,從歸檔的字節流解碼出來的對象會被正常實例化,實例化所用的類與原來編碼的類相同。

一般狀況下,您但願將程序中須要保存狀態的對象歸檔。模型對象幾乎老是屬於這個範疇。您經過編碼將對象寫入到檔案中,而經過解碼將對象從檔案中讀取出來。經過NSCoder對象能夠執行編解碼操做,在編解碼過程當中最好使用鍵化的歸檔技術(須要調用NSKeyedArchiverNSKeyedUnarchiver類的方法)。被編解碼的對象必須遵循NSCoding協議;該協議的方法在歸檔過程當中會被調用。

屬性列表的序列化

屬性列表是一個簡單的、具備必定結構的對象圖序列,它僅使用下面這些類的對象:NSDictionaryNSArrayNSStringNSDataNSDate、和NSNumber。這些對象一般也被稱爲屬性列表對象。Cocoa中有幾個框架類提供了序列化屬性列表對象,以及定義錄寫對象內容及其層次關係的特殊數據流格式的方法。NSPropertyListSerialization類就提供了將屬性列表對象序列化爲XML或其它優化的二進制格式的類方法。

若是對象圖中包含的是簡單對象,則在捕捉和外部化對象及其狀態時,屬性列表序列化是一種靈活的、可移植的、而又很是適當的工具。然而,這種形式的序列化有它的限制,它不保留對象的所有類標識,而只保留一些通常的類型(數組、字典、字符串、等等)。這樣,從屬性列表恢復出來的對象可能和原來的類不一樣,特別是當對象的可變性可能發生變化時,這就會帶來問題。屬性列表序列化也不跟蹤在同一對象中被屢次引用的對象,這可能致使反向序列化時產生多個實例,而在原來的對象圖中卻只有一個實例。

Core Data

Core Data是一個管理對象圖,並使其留存的框架和架構。正是第二種能力—對象的留存能力—使Core Data成爲備忘錄模式的一種適配形式。

在Core Data架構中,中心的對象稱爲被管理對象上下文,負責管理應用程序對象圖中的模型對象。在被管理對象上下文下面是該對象圖的持久棧,也就是一個框架對象的集合,負責協調模型對象和外部數據存儲,好比XML文件或關係數據庫。持久棧對象負責創建存儲中的數據和被管理對象上下文中的對象之間的映射關係,在有多個數據存儲的時候,持久棧對象將這些存儲表現爲被管理對象上下文中的一個聚合存儲。

Core Data的設計也在很大程度上受到模型-視圖-控制器以及對象建模模式的影響。

觀察者模式

這種模式定義一種對象間一對多的依賴關係,使得當一個對象的狀態發生變化時,其它具備依賴關係的對象能夠自動地被通知和更新。觀察者模式本質上是個發佈-定閱模型,主體和觀察者具備寬鬆的耦合關係。觀察和被觀察對象之間能夠進行通信,而不須要太多地瞭解對方。

在iOS經常使用的觀察者模式的模塊有通知、KVO等。

通知

Cocoa的通知機制實現了一對多的消息廣播,其實現方式符合觀察者模式。在這種機制中,程序裏的對象將本身或其它對象添加到一或多個通知的觀察者列表中,每一個通知由一個全局的字符串(即通知的名稱)標識。但願向其它對象發送通知的對象-也就是被觀察的對象-負責建立一個通知對象,並將它發送到通知中心。通知中心則負責肯定通知的觀察者,並經過消息將通知發送給觀察者對象。通知消息激活的方法必須遵循特定的單參數簽名格式,方法的參數就是通知對象,包含通知的名稱、被觀察的對象、以及一個含有補充信息的字典。

通知的發送是一個同步的過程。在通知中心將通知廣播給全部的觀察者以前,發送對象不會再獲得程序的控制權。對於異步的處理方式,控制權會在您將通知放入通知隊列中以後當即返回到發送對象,當通知到達隊列的頂部時,通知中心會將它廣播出去。

常規的通知-也就是那些由通知中心廣播的通知-只能在進程內部傳播。若是您但願將通知廣播給其它進程,須要使用分佈式通知中心及其相關的API。

使用通知能夠有不少緣由。例如,藉助通知機制,您能夠根據程序中其它地方發生的事件改變用戶界面元素顯示信息的方式。或者,您能夠用通知來保證文檔中的對象在文檔窗口關閉以前保存本身的狀態。通知的通常目的是將事件通知給其它程序對象,使它們能夠作出恰當的反應。

可是,通知的接收對象只能在事件發生以後進行反應,這和委託機制有顯著的不一樣。被委託的對象有機會拒絕或修改委託對象但願進行的操做。另外一方面,觀察者對象不能直接影響一個即將發生的操做。

與通知有關的類有NSNotification(通知對象)、NSNotificationCenter(用於發送通知和添加觀察者)、NSNotificationQueue(負責管理通知隊列)、和NSDistributedNotificationCenter。不少Cocoa框架都發布和發送通知,其它對象均可以成爲這些通知的觀察者。

KVO

鍵-值觀察是使對象能夠在其它對象的具體屬性發生變化時獲得通知的一種機制。它基於名爲NSKeyValueObserving 的非正式協議。被觀察的屬性能夠是簡單的屬性、一對一的關係、或者一對多的關係。鍵-值觀察在模型-視圖-控制器模式中特別重要,由於它使視圖對象-經過控制器層-能夠觀察到模型對象的變化,所以是Cocoa綁定技術的必要組件(參見"控制器類"部分)。

Cocoa爲不少NSKeyValueObserving方法提供了缺省的「自動」實現,使全部遵循該協議的對象都具備屬性-觀察的能力。

鍵-值觀察和通告機制相似,但在一些重要的方面也有不一樣。在鍵-值觀察中,沒有爲全部觀察者提供變化通告的中心對象,而是將變化的通告直接傳遞給觀察對象。還有,鍵-值觀察直接和具體的對象屬性相關聯。而通告機制則更普遍地關注程序的事件。

參與鍵-值觀察(KVO)的對象必須知足特定的要求-或者說是遵循KVO,記憶起來更爲方便。對於自動觀察來講,這須要知足鍵-值編碼的要求(也稱爲遵循KVC),並使用遵循KVC的方法(也就是存取方法)。鍵-值編碼是一個與自動獲取和設置對象屬性值有關的機制(基於相關的非正式協議)。

您能夠禁用自動的觀察者通告,並經過NSKeyValueObserving非正式協議及相關範疇中的方法實現手工通告,從而對KVO通告進行精化。

狀態模式

容許一個對象在其內部狀態改變時改變它的行爲。對象看起來彷佛修改了它的類。

import Foundation

class Context {
    var state: IState?
    
    func request() {
        state?.handle()
    }
}

protocol IState {
    func handle()
}

class ConcreteStateA: IState {
    func handle() {
        print("狀態A")
    }
}

class ConcreteStateB: IState {
    func handle() {
        print("狀態B")
    }
}
複製代碼

策略模式

定義一系列的算法,把它們一個個封裝起來, 而且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化。

import Foundation

class Context: IContextInterface {
    var quickStrategy: IStrategy?
    var insertStrategy: IStrategy?
    
    func quickSort() {
        quickStrategy?.sort()
    }
    
    func insertSort() {
        insertStrategy?.sort()
    }
}

protocol IContextInterface {
    func quickSort()
    func insertSort()
}

protocol IStrategy {
    func sort()
}

class QuickSortStrategy: IStrategy {
    func sort() {
        print("快排策略")
    }
}

class InsertSortStrategy: IStrategy {
    func sort() {
        print("插排策略")
    }
}
複製代碼

模板方法模式

這種模式爲某個操做中的算法定義框架,並將算法中的某些步驟推遲到子類實現。模板方法模式使子類能夠重定義一個算法中的特定步驟,而不須要改變算法的結構。

模板方法模式是Cocoa的基本設計,事實上也是通常的面向對象框架的基本設計。Cocoa中的模式使一個程序的定製組件能夠將本身掛到算法上,但什麼時候和如何使用這些定製組件,由框架組件來決定。Cocoa類的編程接口一般包括一些須要被子類重載的方法。在運行環境中,框架會在本身所執行的任務過程當中的某些點調用這些所謂的通常方法。通常方法爲定製代碼提供一個結構,目的是爲當前正在執行且由框架類負責協調的任務加入具體程序的的行爲和數據。

import Foundation

class Client {
    var operationC: AbstractClass?
    
    func operation() {
        //執行
        operationC?.templateMethod()
    }
}

class AbstractClass {
    func templateMethod() {
        print("執行當前邏輯...")
        
        //推遲留給子類處理邏輯...
        primitiveOperation1()
        primitiveOperation2()
    }
    
    func primitiveOperation1() {
        assert(false, "此方法須要繼承")
    }
    
    func primitiveOperation2() {
        assert(false, "此方法須要繼承")
    }
}

class ConcreteClass: AbstractClass {
    override func primitiveOperation1() {
        print("執行operation1邏輯")
    }
    
    override func primitiveOperation2() {
        print("執行operation2邏輯")
    }
}
複製代碼

訪問者模式

表示一個做用於某對象結構中的各元素的操做。它使你能夠在不改變各元素的類的前提下定義做用於這些元素的新操做。

解析UML以下:

  • 定義訪問元素,包括抽象對象與具體對象。
  • 定義訪問者,在抽象對象中訪問者定義執行動做,訪問元素中接收訪問者的方法。訪問元素增長 acceptVisitor(visitor) 方法(接收訪問者),訪問者增長 visitA(A)、visitB(B)、visitC(C) 方法(根據元素對象的多少)。
  • 經過訪問元素調用訪問者中的事件。在訪問元素的 acceptVisitor 的實現方法中調用 [visitor visitX:self] 執行方法。
import Foundation

class Client: NSObject {
    func begin() {
        let visit1 = VisitorA()
        let visit2 = VisitorB()
        let element1 = VisitElementA()
        let element2 = VisitElementA()
        let element3 = VisitElementA()
        let element4 = VisitElementB()
        let element5 = VisitElementB()
        let array = [element1,element2,element3,element4,element5]
        for element in array {
            let number = arc4random()
            if number%2 == 0 {
                element.acceptVisit(visit: visit1)
            }
            else {
                element.acceptVisit(visit: visit2)
            }
        }
    }
}

class Visitor: NSObject {
    /// 訪問元素A
    func visitA(element :VisitElementA)  {
        
    }
    /// 訪問元素B
    func visitB(element :VisitElementB)  {
        
    }
}

class VisitorA: Visitor {
    override func visitA(element: VisitElementA) {
        NSLog("No1 Visit1 %@", element)
        /// 用 element 作某些操做
    }
    
    override func visitB(element: VisitElementB) {
        NSLog("No1 Visit2 %@", element)
        /// 用 element 作某些操做
    }
}

class VisitorB: Visitor {
    override func visitA(element: VisitElementA) {
        NSLog("No2 Visit1 %@", element)
        /// 用 element 作某些操做
    }
    
    override func visitB(element: VisitElementB) {
        NSLog("No2 Visit2 %@", element)
        /// 用 element 作某些操做
    }
}

class VisitElement: NSObject {
    func acceptVisit(visit :Visitor) {
    }
}

class VisitElementA: VisitElement {
    override func acceptVisit(visit :Visitor) {
        visit.visitA(element: self)
    }
}

class VisitElementB: VisitElement {
    override func acceptVisit(visit :Visitor) {
        visit.visitB(element: self)
    }
}
複製代碼

關注我

歡迎關注公衆號:jackyshan,技術乾貨首發微信,第一時間推送。

相關文章
相關標籤/搜索