Swift重新整理Head First設(shè)計(jì)模式

引子:

為什么要運(yùn)用設(shè)計(jì)模式?先有模式還是先有代碼?MVC之間VC,MC他們之間的設(shè)計(jì)模式又是什么?一開始看到《Head First設(shè)計(jì)模式》寫的東西就在想它有沒有價(jià)值。本人目前從事客戶端開發(fā),這個(gè)博客分兩個(gè)部分來說,第一個(gè)是講講各個(gè)模式,大概是書上模式的復(fù)刻版,基本上用swift重寫了一遍。第二說說復(fù)合模式的相關(guān)理解和總結(jié)。這里面的代碼部分其實(shí)不重要,重點(diǎn)看的是為什么這么寫,如果真的想圖文并茂的看這些東西,那么可以去看看書上,這本書寫的非常好。如果想簡單的獲得設(shè)計(jì)模式的相關(guān)概括,那么我會(huì)盡量簡潔并且保持一定的趣味和思考空間。另外原來的書是Java寫的,本文是用的Swift,語法方面的東西我們暫時(shí)不做討論。

(tip:這個(gè)后期我可能還會(huì)不停的添加相關(guān)的理解,先做一個(gè)說明,iOS 里面的NSCondition對(duì)應(yīng)的生產(chǎn)者和消費(fèi)者模式,有時(shí)間我回來整理一下。)

第一部分-模式介紹:

> 策略模式:

定義:定義了算法簇,分別封裝起來,讓他們可以相互替換,這個(gè)模式讓算法的變化獨(dú)立于使用算法的用戶。

注解:有點(diǎn)蒙?我也是。再來看看原則:針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程。如果你熟悉swift,沒錯(cuò)就是針對(duì)協(xié)議編程。

先來想一個(gè)問題(書上的例子):
一個(gè)鴨子和一個(gè)飛機(jī)的共同點(diǎn)是什么?
1.會(huì)飛。2.有翅膀。OK,足夠了。
如果你看到這里一定對(duì)面對(duì)對(duì)象有不少了解,那么一開始的代碼可能是:

//swift code

class BaseClass {
    
    var wingsName:String = ""
    
    func fly(){}
    
}

class Duck:BaseClass {
    
    override func fly() {
        print("Duck Fly")
    }
    
}

class Plane:BaseClass {
    
    override func fly() {
        print("Plane Fly")
    }
    
}

Duck().fly()
Plane().fly()

/*輸出結(jié)果
 Duck Fly
 Plane Fly
 */

代碼:問題搞定了,可是我們寫代碼的偉大事業(yè)難道不是為了節(jié)省下更多勞動(dòng)力嗎?確實(shí)如此,考慮一下這時(shí)候又有一個(gè)塑料鴨子,我們?nèi)绾稳?fù)用上面的代碼呢,塑料鴨子有翅膀,但是并不能自由飛翔。我們甚至不得不重寫一個(gè)類來表示塑料鴨子,那么就是說我們感覺有共同點(diǎn)的東西并沒有做到代碼的復(fù)用。如何? 重寫!

//swift code

protocol FlyProtocol {
    
    func fly()
    
}


protocol WingsProtocol {
    
    var wingsName:String {get}
    
}

protocol MusicProtocol { //能播放電子音樂。
    
    func music()
}


class Duck:FlyProtocol,WingsProtocol {
    
    var wingsName: String = "Duck Wings"
    
    func fly() {
        print("Duck fly")
    }
    
}

class Plane:FlyProtocol,WingsProtocol {
    
    var wingsName: String = "Plane Wings"
    
    func fly() {
        print("Plane fly")
    }
    
}

//重點(diǎn)這里

class PlasticDuck:WingsProtocol,MusicProtocol {
    
    var wingsName: String = "PlasticDuck wings"
    
    func music() {
        print("di da di da di ...")
    }
    
}

代碼寫完了,我想說的是,在坐的各位........有什么感覺嗎?我們把方法的規(guī)則用協(xié)議(Java里面叫接口)的方式重新寫了一下,這樣滿足需要的就去實(shí)現(xiàn)這個(gè)協(xié)議。所以我們可以分解這個(gè)世界。。我們把那個(gè)活生生的鴨子也給分解了...并不是吃肉了,而是看做他是由翅膀、頭、爪子等構(gòu)成的物品,飛機(jī)也是,塑料鴨子也是,這樣我們就可以分開去描述他們的組合方式。這樣有再多的東西來了進(jìn)行組合我們 三千東流水 取幾瓢便是。這也是Swift里面主張的面向協(xié)議的核心思想。

原則:多用組合,少用繼承。 (萬年不變的當(dāng)然還是用繼承比較穩(wěn)妥)

(另外swift里面的協(xié)議擴(kuò)展是非常重要的! 后面如果可能還有一個(gè)關(guān)于swift 面向協(xié)議的文章分享,暫時(shí)放這里如果有我就回來修改添加上連接。另外下面的例子就不舉反例了,知道模式非常有用就行。)

> 單例模式:

定義:確保一個(gè)類只有一個(gè)實(shí)例,并且提供一個(gè)全局的訪問點(diǎn)。

注解:就是平時(shí)使用的各種Tool,通用方法等等。。

代碼:

class MyManager  {
    static let shared = MyManager()
    private init() {} // 這個(gè)需要注意一下添加上。
}

swift 100 tips 中單例模式中摘抄的原理:

在初始化類變量的時(shí)候,Apple 將會(huì)把這個(gè)初始化包裝在一次 swift_once_block_invoke 中,以保證它的唯一性。不僅如此,對(duì)于所有的全局變量,Apple 都會(huì)在底層使用這個(gè)類似 dispatch_once 的方式來確保只以 lazy 的方式初始化一次。

另外,我們在這個(gè)類型中加入了一個(gè)私有的初始化方法,來覆蓋默認(rèn)的公開初始化方法,這讓項(xiàng)目中的其他地方不能夠通過 init 來生成自己的 MyManager 實(shí)例,也保證了類型單例的唯一性。如果你需要的是類似 default 的形式的單例 (也就是說這個(gè)類的使用者可以創(chuàng)建自己的實(shí)例) 的話,可以去掉這個(gè)私有的 init 方法。

單例模式可以看看下面觀察者中的使用方式~

> 觀察者模式:

定義:定義了對(duì)象之間的一對(duì)多的依賴,這樣當(dāng)一個(gè)對(duì)象改變狀態(tài)的時(shí)候他的所有依賴者都會(huì)接到通知并自動(dòng)更新。 (Swift,OC開發(fā)者都應(yīng)該很熟悉觀察者模式了,對(duì)于OC,這里突然想到了防止崩潰的方法里面就一個(gè)是針對(duì)觀察者的,后期整理。)

注解:這個(gè)和通知中心的概念其實(shí)差不多,都是寫的訂閱和發(fā)送的關(guān)系。這樣設(shè)計(jì)的好處是對(duì)象之間的松耦合性,對(duì)象之間的依賴降到了最低。個(gè)人理解這個(gè)模式要避免使用過多,因?yàn)閷?duì)象依賴是降低了,可是熵增加了,有點(diǎn)亂啊,大兄弟!

代碼:系統(tǒng)是用相關(guān)的方法實(shí)現(xiàn)在NSObject里面的。不過這里自己實(shí)現(xiàn)一個(gè)加深一下印象吧。

import  UIKit

protocol CouldObserve {
    
    var className:String {get}
    
    func updateObserveWeatherData(newTemp:String)
    
}

// 這里面是 extension 然后又一定限定的形式。
extension CouldObserve where Self:Equatable {
    
    //static var identify:String {
    
    //String.init
    //return (String.init(utf8String: object_getClass(self) ?? ""))?.components(separatedBy: ".")
    //}
}
// 觀察者模式, 不能因?yàn)橥ㄖ捻樞虿煌a(chǎn)生額外的錯(cuò)誤?。?每一個(gè)觀察者是獨(dú)立的。
class WeatherObserve {
    
    private var obsList:[CouldObserve] = [CouldObserve]() //創(chuàng)建一個(gè)待觀察的列表。
    private var newTemp:String = "0"
    //var currentObserve:WeatherObserve?// = WeatherObserve() ----
    
    static let currentObserve = WeatherObserve() // 注意這里的 單例 寫法。。
    private init () {}
    
    var changed = false
    
    static func getCurrentObserve() -> WeatherObserve { // 獲取對(duì)應(yīng)的 觀察站
        return currentObserve // 這里需要返回一個(gè)單例形式的變量 單例上面的寫法那個(gè)是有問題的。
    }
    
    func addObserve(new:CouldObserve) {
        self.obsList.append(new)
        
    }
    
    func setChanged() { // 增加一個(gè)容錯(cuò)機(jī)制。 添加上這個(gè)可以控制一下流程。
        
        changed = true
    }
    
    func removeObserve(rem:CouldObserve) {
        
        let index = self.obsList.index(where: { (element) -> Bool in
            return element.className == rem.className
        }) // 盡量分開寫要不容易出問題。
        self.obsList.remove(at: index!)
    }
    
    func updataNewTemp() { //發(fā)送通知。
        for (i, v) in self.obsList.enumerated() {
            v.updateObserveWeatherData(newTemp: newTemp + "- \(i) -new")
        }
    }
    
    var newMessage: String = "0" {
        
        didSet {
            print("newMessage set")
            
            if changed == true {
                self.newTemp = newMessage
                self.updataNewTemp()
            }
            
            changed = false
            
        }
        
    }
    
}


class User:CouldObserve {
    
    func updateObserveWeatherData(newTemp: String) {
        print("newTemp = \(newTemp)")
    }
    
    
    var className: String {
        return String.init(cString: class_getName(object_getClass(self))) //獲取類的名字
    }
    
    
    func regist() {
        
        WeatherObserve.getCurrentObserve().addObserve(new: self)
        
    }
    
    
    func remove() {
        
        WeatherObserve.getCurrentObserve().removeObserve(rem: self)
        
    }
    
    
}

let ttt = User()
ttt.regist()

WeatherObserve.getCurrentObserve().newMessage = "456"

/*
 輸出結(jié)果 newMessage set
 */

估計(jì)蘋果的底層也是這么個(gè)機(jī)制吧,不過一定沒有這么簡單。?? 另外注意其中的單例寫法別忘記了 private init() {}。

> 裝飾著模式

定義:動(dòng)態(tài)的將責(zé)任添加到對(duì)象要擴(kuò)展的功能上,若要擴(kuò)展功能,裝飾者提供了比集成更有彈性的代替方案。

注釋:書上這種模式可以用在大面積的零散部件(類型相同),的情況加做挑挑揀揀的累加處理。例如一個(gè) 一杯咖啡的設(shè)計(jì), 里面添加的作料和咖啡本身都是可供銷售的商品,我們需要的是一個(gè)對(duì)象能裝點(diǎn)所有的東西,然后一起計(jì)算價(jià)格。

代碼:

import UIKit

var str = "Hello, playground"


protocol PriceProtocol { //
    
    var price:Int {get}
    
}

class Good { //這個(gè)作為一個(gè)父類 內(nèi)容是基本上不會(huì)變化的 所以沒什么必要進(jìn)行抽離使用繼承.
    
    var basePrice:Int {
        get {
            return 0
        }
    }
    
    var goodsAdd:Good? = nil
    
    required init(goods:Good? = nil){
        goodsAdd = goods
    }
    
    var totalPrice:Int? {
        get {
            return basePrice +  (goodsAdd != nil ? goodsAdd!.totalPrice ?? 0 : 0) //注意這里面加的是totalPrice 而不是basePrice
        }
    }
}

//作料
/*
    這里寫的兩步倒是挺麻煩的。。因?yàn)閟wift不能在 protocol extension 或者是 繼承的時(shí)候繼承存儲(chǔ)屬性,所以只能通過一個(gè)協(xié)議寫一個(gè)限制,保證類實(shí)現(xiàn)這個(gè)價(jià)格,然后通過計(jì)算屬性對(duì)這個(gè)價(jià)格進(jìn)行一個(gè)返回的操作。這個(gè)不支持繼承存儲(chǔ)感覺有點(diǎn)傻,但是沒辦法~ 如果有好方法請告訴我。~
 */
class Milk:Good,PriceProtocol {
    
    var price: Int = 2
    override var basePrice: Int { return price }
    
}
class Suger:Good,PriceProtocol {
    
    var price: Int = 3
    override var basePrice: Int { return price }

}


//可供出售的coffee
class MaoShiCoffee:Good,PriceProtocol {
    
    var price: Int = 10
    override var basePrice: Int { return price }

}

class LajiCoffee:Good,PriceProtocol {
    
    var price: Int = 20
    override var basePrice: Int { return price }

}


//現(xiàn)在來一份添加 牛奶和糖的貓屎咖啡

var coffee:Good = MaoShiCoffee() //這里面 必須要規(guī)定一下 是Good類型要不下面就類型出錯(cuò)了。
coffee.totalPrice //10
coffee = Milk.init(goods: coffee)
coffee.totalPrice //12
coffee = Suger.init(goods: coffee)
coffee.totalPrice //15

注意:
留意看其中的注釋,因?yàn)閟wift的一些類型限制,還有繼承的一些限制,所以代碼看著有點(diǎn)重復(fù),也沒啥好辦法,如果有,請留言??。

> 工廠方法:

定義:了一個(gè)創(chuàng)建對(duì)象的接口,但是由于子類決定要實(shí)例化的類是哪一個(gè)。工廠方法讓類把實(shí)例化推遲到子類。

原則:依賴倒置原則--> 要依賴抽象,不要依賴具體的類。

注釋:工廠方法,以前總感覺沒什么用,好像就是創(chuàng)建一個(gè)UIButton這樣簡單的功能,我也問過自己為什么不用一個(gè)switch直接搞定得了???后來發(fā)現(xiàn)了工廠方法還分為簡單工廠和抽象工廠。利用上述的原則,我們分析一個(gè)問題要從最后的結(jié)果到最前面的分析過程,拿買餅這樣一個(gè)例子來說,有不同的餅店,不同的餅店里面可能有相同名字的餅,比方說多個(gè)店里面都有“醬香餅”,我們可以去 “大望路店”去買,也可以去“中關(guān)村店”去買,而這兩個(gè)店鋪又可以去不同的工廠訂購需要的餅,拿回來的餅又可以進(jìn)行二次的加工,比方說可以打包或者添加佐料。OK,有點(diǎn)繞暈了。我也是啊,兄弟!來吃點(diǎn)藥,不要暈。。。

分析:如果沒有學(xué)習(xí)工廠方式的設(shè)計(jì)原則,那么我們處理這個(gè)問題的方式應(yīng)該是從工廠寫起來,有了工廠然后創(chuàng)建的對(duì)象傳遞給店鋪店鋪加工之后再給顧客。。。這么寫沒錯(cuò),可是店鋪可以增加減少倒閉,工廠也不一定是一個(gè),甚至加工方式佐料都可能是后期添加減少的選項(xiàng),拿到手的東西經(jīng)過這一套的處理,估計(jì)很難對(duì)整個(gè)系統(tǒng)擴(kuò)展。。因?yàn)槲覀円蕾嚨脑瓌t是最后生產(chǎn)出來的產(chǎn)品。。(什么?你壓根就沒有考慮這個(gè)問題?。。) 現(xiàn)在反過來,我們拿到的是商品對(duì)象,所以要有店鋪, 商品和店鋪上游的工廠 是沒有任何關(guān)系的,一個(gè)店鋪要有生產(chǎn)好多種商品的能力,也不可能僅僅從一個(gè)廠商去進(jìn)購商品。

我們大概需要一個(gè)這樣的購買方式:


購買餅的方式.png

不同的店鋪去買不同的pizza(餅),給出的餅里面可能會(huì)有不同的作料(例如 shit)的添加。。。

我們看看店鋪的方法:

Store.png

這里使用協(xié)議規(guī)范了開店的需求,然后通過order方法可以進(jìn)行分發(fā)操作,同時(shí)調(diào)用本身的creat 來進(jìn)行制作,因?yàn)閒actory也是滿足了各種規(guī)范也就能愉快的執(zhí)行creat中的三種操作了。

分析一下,這個(gè)store是不和factory有任何耦合性的,store可以個(gè)性化的定制自己需要的factory,不同的店鋪由自己的內(nèi)部決定。

再來看看Pizza(就是我們說的餅店里賣的餅)的結(jié)構(gòu)。


pizza--->就當(dāng)做我們的餅.png

這里沒有什么特別的就是一些限定方法,實(shí)現(xiàn)的類里面給出自己獨(dú)特的做法(北京的餅可能就添加了shit,其他的就沒有這個(gè)服務(wù)。)

再看看factory。


factory.png

到了這里 回頭再看看,我們買餅的過程,stroe 和 factory 是分開的 factory 和 pizza也是分開的。三者之間除了調(diào)用上的聯(lián)系之外沒有任何聯(lián)系了。所以是解耦的。其實(shí)我個(gè)人理解設(shè)計(jì)模式就是用來解耦合的,這樣才能易于擴(kuò)展,所以不要指望外包的代碼寫的很規(guī)范~~~~

完整代碼如下:

import UIKit

// 我要做餡餅, 現(xiàn)在要由工廠來生產(chǎn) , 工廠可以擴(kuò)展, 餡餅可以增加, 餡餅店可以訂購不同的商品, 餡餅也是可以更換材料和做法,

// 倒置依賴原則


protocol Amatore {
    var price:String{get}
}

class Proto:Amatore { //西紅柿
    var price: String = "2"
}

class Tomato:Amatore { // 土豆子~??
    var price: String = "3"
}


/****餡餅****/
protocol Pizza { //我們的餡餅 不一定是什么地方的 但是規(guī)定一個(gè)有 基礎(chǔ)價(jià)格和添加方法
    func price() -> Int

    var basePrice : Int {get set}
    
    func priceAdd(add:Int)
}


class PizzaBeijing:Pizza {
    internal func price() -> Int {
        return basePrice
    }
 //北京烤鴨
    
    var basePrice : Int = 250
    
    func priceAdd(add:Int)  {
        basePrice = add + basePrice
    }
    
    static func addSault(piz:PizzaBeijing) {
        piz.priceAdd(add: 100)
    }
    
    static func addShit(piz:PizzaBeijing)  {
        piz.priceAdd(add: 200)
    }
    
}


class PizzaShandong:Pizza {
    
     func price() -> Int {
        return basePrice
    }
    
    var basePrice : Int = 250
    
    func priceAdd(add:Int)  {
        basePrice = add + basePrice
    }
    
    static func addShit(piz:PizzaShandong)  {
        print("in addShit")
        piz.priceAdd(add: 1000)
    }
    
}


/****工廠****/

protocol Company { // 抽象工廠
    
    func cook(piz:Pizza) -> Pizza
    func box(piz:Pizza) ->Pizza
    func creat() -> Pizza
}

class TaishanFactory:Company {
    func creat() -> Pizza {
        return PizzaBeijing()
    }

    internal func box(piz: Pizza) -> Pizza {
        
        return piz
    }

    internal func cook(piz: Pizza) -> Pizza {
        return piz
    }
}


class HangzhouFactory:Company {
    
    internal func creat() -> Pizza {
        return PizzaShandong()
    }
    
    internal func box(piz: Pizza) -> Pizza {
        return piz
    }
    
    internal func cook(piz: Pizza) -> Pizza {
        return piz
    }

}



/***餅店****/

protocol Store {
    
    static func order(name:String) -> Pizza? //可以寫成 static 供類使用 用戶訂購
    static func creat(fac:Company) -> Pizza?
    
}

class StoreDawanglu:Store {

 // 大望路店簽約店鋪是 taishanFactory -> 擴(kuò)展了
    static func creat(fac: Company) -> Pizza? {
        
        var pizza : Pizza? = nil
        
        pizza = fac.creat()
        pizza = fac.cook(piz: pizza!)
        pizza = fac.box(piz: pizza!)
        
        return pizza
        
    }
    
     static func order(name :String) -> Pizza? {
        
        var bing : Pizza? = nil
        
        switch name {
            
        case "蔥花": //蔥花
            bing = self.creat(fac: TaishanFactory())
        case "醬香": // 醬香
            bing = self.creat(fac: HangzhouFactory())
            
        default:
            return nil
        }
        
        
        return bing
        
    }
}

class StoreZhongguancun:Store { //完全返回nil 停業(yè)了~
    
    static func creat(fac: Company) -> Pizza? {
        return nil
    }
    
    static func order(name :String) -> Pizza? {
        
        return nil
    }
}
// 醬香
let bingS = StoreDawanglu.order(name: "醬香") //創(chuàng)建
bingS?.price()
PizzaShandong.addShit(piz: bingS as! PizzaShandong) // 添加作料 作料1000塊錢
bingS?.price()

//開始買餅
let bing = StoreDawanglu.order(name: "蔥花")
bing?.price()
PizzaBeijing.addSault(piz: bing as! PizzaBeijing)
bing?.price() // 給錢


let bing2 = StoreZhongguancun.order(name: "醬香") //倒閉了
bing2?.price()

總結(jié)一下:我們做的這些工作都是為了解除耦合性,增加可讀性,維護(hù)性。上面這個(gè)過程可以簡答理解為需求倒置,從下至上的原則,形狀是一個(gè)倒三角。。其實(shí)我并沒有做過大型的后臺(tái),對(duì)這個(gè)理解的層次不是那么深刻,如果有高手看到請留言告訴我,我一定加你好好請教。。

> 命令模式

定義:將請求封裝成對(duì)象,以便使用不同的請求,隊(duì)列或日志來參數(shù)化對(duì)象。命令模式一般支持撤銷的操作。

注釋:這個(gè)模式可以想象成一個(gè)遙控器,只是負(fù)責(zé)開關(guān)的操作,遙控器本身并不知道具體的工作是什么。也可以理解為餐廳里面的傳餐員,他并不知道廚師要做什么,只是把單子上的餐品名稱傳遞給了后廚。這個(gè)方式和后面要寫的狀態(tài)模式有些類似。不過并不是完全一樣。

下面就拿一個(gè)遙控器的例子來說吧:

首先看看要被控制的東西是一個(gè)空調(diào)簡單稱為Air:


Air牌子的空調(diào).png

這個(gè)是實(shí)際空調(diào)的執(zhí)行方法,里面可以弄各種各樣的操作,不過規(guī)定(protocol)中需要有的方法是 on off unknow unDo, 這四個(gè)方法。

遙控器.png

遙控器中有一個(gè)elementCommands這個(gè)是告訴讀者可以這樣去限定遙控器的按鍵數(shù)量,這里直接使用了一個(gè)elementCommand 代表著有兩個(gè)東西需要控制,一個(gè)0號(hào)按鍵是Air對(duì)應(yīng)的空調(diào),一個(gè)1號(hào)按鍵light對(duì)應(yīng)的燈。lastCommand記錄一下用來做撤銷操作。其他的實(shí)現(xiàn)方法應(yīng)該就一目了然了。

然后就可以通過RemoteControl.shared.electicalOnNumber(index: 1) 這樣的遙控器單例進(jìn)行操作對(duì)應(yīng)的按鍵操作了。

完整代碼:

import UIKit

var str = "Hello, playground"


// 寫一下 order 模式  


// 寫一個(gè)家居的模式,


// 兩個(gè) 電氣

protocol Electrical {
    
}

class AirConditioner : Electrical {
    
}

class Lighting : Electrical {
    
}


protocol CommandProtocol { //規(guī)定這個(gè)家用電器的狀態(tài)
    
    associatedtype E:Electrical
    
  
}

protocol CommandFunc { // 規(guī)定
    
    func on() -> CommandFunc
    func off() -> CommandFunc
    func unknow() -> CommandFunc
    func unDo() -> CommandFunc
    
}
// 要知道程序的世界里面部分男女老少 沒有貧賤之分, 所以 被人使用了千百遍的代碼 和新寫出而來的代碼 在使用平等性上幾乎一樣。
// 這地方注意類型
class CommandAir:CommandProtocol,CommandFunc {
    
    //假定這個(gè)是空調(diào)遙控器
    typealias E = AirConditioner
    
    var currentStatus:String = "unKnow"
    var lastStatus:String = "unKnow"
    
    
    func unknow() -> CommandFunc {
        print("空調(diào)不知道什么狀態(tài)")
        return self
    }

    func off() -> CommandFunc {
        print("空調(diào)關(guān)了啊啊啊")
        return self
    }

    func on() -> CommandFunc {
        print("空調(diào)開了")
        return self
    }
    
    func unDo() -> CommandFunc {
        print("撤銷操作")
        return self
    }

}

class CommandLight:CommandProtocol,CommandFunc {
    
    typealias E = Lighting
    
    func unknow() -> CommandFunc {
        print("燈不知道什么狀態(tài)")
        return self
    }
    
    func off() -> CommandFunc {
        print("燈關(guān)了啊啊啊")
        return self
    }
    
    func on() -> CommandFunc {
        print("燈開了")
        return self
    }
    func unDo() -> CommandFunc {
        print("撤銷操作")
        return self
    }
}

class CommandStatusAll:CommandFunc {
    
    var commands:[CommandFunc] = [CommandFunc]()
    
    func unknow() -> CommandFunc {
        
        return self
    }
    
    func off() -> CommandFunc {
        
       
        
        commands.forEach({ (element) in
            let _ = element.off()
        })
        
        return self
    }
    
    func on() -> CommandFunc {
        
        print(commands)
        
        commands.forEach({ (element) in
            let _ = element.on()
        })
        
        return self
    }
    func unDo() -> CommandFunc {
        
        return self
    }
    
}

// 這里是一個(gè)遙控器

class RemoteControl {
    
    // 遙控器設(shè)置成單例
    static let shared:RemoteControl = RemoteControl()
    
    private init () {}
    
    // 假設(shè)現(xiàn)在遙控器上面有兩個(gè)按鈕是開, 兩個(gè)按鈕對(duì)應(yīng)的是是關(guān)。 一個(gè)按鈕是取消。
    var elementCommand : [CommandFunc] = [CommandAir(),CommandLight()]
    
    var elementCommands = Array.init(repeating: CommandFunc.self, count: 6) //設(shè)置最多按鍵數(shù)量
    
    var lastCommand:CommandFunc?
    
    func electicalOnNumber(index:Int) {
        elementCommand[index].on()
    }
 
    func eleticalOffNumber(index:Int) {
        
        elementCommand[index].off()
    }
    
    func unDoClick() {
        
        lastCommand?.unDo()
    }
    
    func elementInsert(element:CommandFunc,index:Int) { // 功能/對(duì)應(yīng)的位置
        elementCommand.replaceSubrange(Range.init(uncheckedBounds: (index,1)), with: [element]) //從index開始的第幾個(gè)元素
        print(elementCommand)
    }
    
}



// 現(xiàn)在添加一個(gè)插槽的操作


RemoteControl.shared.elementInsert(element: CommandLight(), index: 1) //實(shí)現(xiàn)了一個(gè)插座的功能可以增加元素。

RemoteControl.shared.electicalOnNumber(index: 1)
RemoteControl.shared.electicalOnNumber(index: 0)
RemoteControl.shared.unDoClick()
RemoteControl.shared.eleticalOffNumber(index: 0)
RemoteControl.shared.eleticalOffNumber(index: 1)
RemoteControl.shared.unDoClick()

let cAll = CommandStatusAll()
cAll.commands = RemoteControl.shared.elementCommand
RemoteControl.shared.elementInsert(element: cAll, index: 0) //替換
RemoteControl.shared.electicalOnNumber(index: 0)

總結(jié):命令模式 旨在命令和實(shí)現(xiàn)是完全解耦的, 控制的一方只是通知其中保存的對(duì)象執(zhí)行相應(yīng)的動(dòng)作,至于成功失敗操作過程是不關(guān)心的。這種上層的邏輯如果在swift中可以對(duì)幾個(gè)頁面發(fā)送通知,或者是一個(gè)頁面的不同控件做對(duì)應(yīng)的操作。如果按照流程進(jìn)行一波操作就和狀態(tài)模式有點(diǎn)像了,狀態(tài)模式的時(shí)候再說。

> 適配器模式

定義:將一個(gè)類的接口轉(zhuǎn)換為客戶期望的另一個(gè)接口,適配器讓原本接口不兼容的類可以相互合作。

注釋:這個(gè)好像就是說 兩個(gè)類之間寫一個(gè)中間層,輸入和轉(zhuǎn)換對(duì)應(yīng)的參數(shù),讓程序正常運(yùn)行。這個(gè)模式和裝飾器模式的對(duì)比
裝飾器:不改接口但是加入責(zé)任。
適配器:將一個(gè)接口封裝成另一個(gè)接口。

這個(gè)模式我沒有好的例子,書上看的也有點(diǎn)不透徹,暫時(shí)先放這里。

> 模板方法模式&鉤子模式

模板方法:抽離主要步驟,具體類具體實(shí)現(xiàn)。
鉤子方法:實(shí)現(xiàn)一些空的返回,或者是Bool這些簡單的類型返回,如果子類繼承了可以重寫相關(guān)方法實(shí)現(xiàn)對(duì)流程的控制。這種個(gè)人感覺有點(diǎn)類似于子類對(duì)父類的一些流程控制。


流程控制.png

代碼:

//: Playground - noun: a place where people can play

import UIKit

var str = "Hello, playground"


class Person {
    
    func tesss() {
        method1()
        method2()
        method3()
        if method4() { return }
        method5()
        method6()
    }
    
    func method1()  {
        print( #function )
    }
    
    func method2() {
        print( #function )
    }
    
    func method3() {
        print( #function )
    }
    
    func method4() -> Bool{
        print( #function )
        return false
    }
    
    func method5() {
        print( #function )
    }
    
    func method6() {
        print( #function )
    }
    
}

class p1 : Person {
    override func method4() -> Bool {
        return true //控制流程
    }
}

Person().tesss()
print("1")
p1().tesss()

tip:這個(gè)上面的代碼雖然簡單的很,我甚至都不想寫。不過如果這個(gè)步驟是一個(gè)初始化過程,類里面有很多變量需要初始化的時(shí)候呢?有的是空有的是可選類型的時(shí)候呢。。如果類似于一個(gè)控件上面需要隱藏和顯示某些顏色等狀態(tài)呢?。。

> 狀態(tài)模式

定義:允許對(duì)象在內(nèi)部狀態(tài)改變的時(shí)候改變它的行為,對(duì)象看起來好像修改了它的類。

注釋:這個(gè)類型和我有點(diǎn)淵源,所以這里面有額外的注意。這個(gè)模式其實(shí)個(gè)人感覺是應(yīng)對(duì)多輸入時(shí)候的解決方案,比方說一個(gè)糖果售賣機(jī)在執(zhí)行一條指令的時(shí)候,你如果隨便去亂按的話,大部分按鍵是沒有作用的,因?yàn)槠帘蔚袅耍贿^關(guān)機(jī)這樣的特殊按鍵還是會(huì)起作用,所以針對(duì)不同時(shí)刻的多種輸入狀態(tài)所發(fā)生的動(dòng)作處理過程,挺適合用這個(gè)狀態(tài)模式的。(注意命令模式和這個(gè)的區(qū)別,命令不會(huì)涉及到任何的執(zhí)行過程,而這個(gè)狀態(tài)模式是自己去維護(hù)狀態(tài)在不同時(shí)刻的狀態(tài)。有點(diǎn)相同的是 二者的控制部分都不需要參與具體的邏輯實(shí)施。)

下面以一個(gè)彈球游戲機(jī)GumballGame作為例子,大概就是投幣進(jìn)去,然后一轉(zhuǎn)到指定位置就可以給出一個(gè)禮物(別告訴我你沒有玩過這種坑爹的游戲?如果有就想想一下這種機(jī)器就在你眼前。)。機(jī)器可以與外界的交互有 投入錢幣,退出錢幣,轉(zhuǎn)動(dòng)手柄。

所以寫一個(gè)機(jī)器,我們把這些方法都實(shí)現(xiàn)進(jìn)來:

GumballGame.png

再想一下機(jī)器可能處在的狀態(tài)可能是什么樣的呢?

1.賣光了(沒有球可以出來了,雖然不太可能)
2.沒投幣
3.投幣了
4.正在旋轉(zhuǎn)羅盤

然后就結(jié)束了。。。
這里面我們需要考慮一下,例如,正在旋轉(zhuǎn)的時(shí)候我選擇了退幣,這個(gè)時(shí)候已經(jīng)不能退幣了,但是如果在投幣狀態(tài)下是可以的,所以我們說這個(gè)退幣的動(dòng)作在GumballGame旋轉(zhuǎn)的時(shí)候是無效的,在GumballGame投入錢幣后是有效的。根據(jù)這個(gè)例子來看,這種狀態(tài)一旦是多起來,我們需要些多少個(gè) if else 才能把所有的情況都表示完?2的...次方??不知道是不是正解的理解:狀態(tài)機(jī)模式可以有效的去掉程序里面 if else 的數(shù)量,讓每一個(gè)狀態(tài)下面都有獨(dú)立的判斷。這么說來,每個(gè)狀態(tài)都是一個(gè)獨(dú)立的對(duì)象去處理相關(guān)的邏輯了...

給出狀態(tài)的代碼:


State.png

protocol 規(guī)定出每個(gè)狀態(tài)對(duì)應(yīng)的方法,各個(gè)state去實(shí)現(xiàn)每個(gè)狀態(tài)。
這個(gè)狀態(tài)和狀態(tài)機(jī)實(shí)現(xiàn)的狀態(tài)方法流程上是一樣的,每一個(gè)單獨(dú)的狀態(tài)里面都具有獨(dú)立處理所有情況的能力。。。這里沒有寫成一樣的為了看的更加清晰一點(diǎn)。

特別注意一下這里面的require 修飾的init方法。這里要求包含一個(gè)狀態(tài)機(jī),然后上面的方法中有效的是InsertQuarter()執(zhí)行了就會(huì)通過狀態(tài)機(jī)去更改成下一個(gè)狀態(tài),所以,我們不要錯(cuò)誤的理解為狀態(tài)機(jī)控制了狀態(tài),而是狀態(tài)會(huì)自己有一個(gè)流程去不停的告訴狀態(tài)機(jī), 狀態(tài)機(jī)接收到外界輸入后再去調(diào)用狀態(tài)執(zhí)行相關(guān)的操作。有點(diǎn)繞?是的,沒關(guān)系。先記住這里改變狀態(tài)的方式是通過狀態(tài)機(jī)去切換下一個(gè)狀態(tài)。

再回到狀態(tài)機(jī)里面:

State初始化.png

狀態(tài)機(jī)里面包含了各個(gè)狀態(tài)。等待被切換。再來看看狀態(tài)的切換過程你是不是沒有注意到初始化狀態(tài)機(jī)時(shí)候的那個(gè)圖片中包含了如下:


狀態(tài)機(jī)切換的過程.png

注意,這里面的state可不一定是同一個(gè)state 啊,可能是初始化過的任何一個(gè)狀態(tài),當(dāng)調(diào)用了各自有效的方法之后進(jìn)行了改變之后的state,如果這里不明白了可以在看看上面state的定義和轉(zhuǎn)換。

另外一個(gè)全局的狀態(tài)變量這個(gè)就不多說了:


全局狀態(tài).png

下面是調(diào)用方式:


調(diào)用方式.png

總結(jié):
讓state去決定下一步應(yīng)該做什么。 每一個(gè)state 里面又有自己的獨(dú)特處理方式,(滿足一整套的流程 縱向流程), 自己處理完成之后可以通過自身持有的 machine 對(duì)象主動(dòng)切換調(diào)用下一個(gè)狀態(tài)。 讓原本需要大量 if else 的判斷邏輯成為主動(dòng)的流程性的東西。通過這個(gè)流程控制清晰的展現(xiàn)出了各個(gè)環(huán)節(jié)的內(nèi)容 不同狀態(tài)處理相關(guān)方法會(huì)得到不同的輸出結(jié)果。非常方便的就可以添加上更多的狀態(tài)。 另外可以在自己各自的狀態(tài)中進(jìn)行事件的處理和分發(fā)。

代碼:

import UIKit


// 這里測試一下狀態(tài)機(jī)模式。

/*
enum State:Int { //這里是狀態(tài)標(biāo)志位。
    
    case  SOLD_OUT = 1
    case  NO_Quarter
    case  Has_Quarter
    case  SOLD
    
}
*/
/* 這樣搞還不太行??
enum State:MechineFunc { //這里是狀態(tài)標(biāo)志位。
    
    case  SOLD_OUT = SoldOutState.init(machine: self)
    case  NO_Quarter = NoquarterState.init(machine: self)
    case  Has_Quarter = HasQuarterState.init(machine: self)
    case  SOLD = SoldState.init(machine: self)
    
}
*/

protocol GumballGame {

    func insertQuarter()
    func ejectQUarter()
    func turnCrank()
}

class GumballMachine:GumballGame {
    
    
    /* 這里是游戲需要的方法。 */
    func insertQuarter() {
        
        state?.InsertQuarter()
    }
    
    func ejectQUarter() {
        state?.ejectQuarter()
    }
    
    func turnCrank() {
        state?.turnCrank()
        state?.dispense()
    }
    //m
    
    var count : Int
    
    // 這個(gè)地方一定會(huì)出問題的, 因?yàn)楹竺?get 里面調(diào)用了self.state 這個(gè)會(huì)出現(xiàn)一個(gè)無限循環(huán)
    /*
    var state : MechineFunc? {
        
        set {
            self.state = newValue
        }
        get {
            print("get state")
            if self.state == nil { print("self.state == nil") ; return soldOutState } //如果這個(gè)是nil 那么就返回賣光了
            print("what state")
            return self.state
        }
    } //默認(rèn)給一個(gè)賣光了
    */
    var state : MechineFunc? {
        didSet {
            print("=============> current status \(state!) ")
        }
    }
    /*
    var soldOutState:MechineFunc
    var noquarterState:MechineFunc
    var hasQuarterState:MechineFunc
    var soldState:MechineFunc
    */
    /* 
        這樣形成了 一個(gè)狀態(tài)模式, 這里面的模式都是可以提供給這個(gè)狀態(tài)機(jī)進(jìn)行增加和刪除的。
     */
    
    lazy var soldOutState : SoldOutState = {
        return SoldOutState.init(machine: self)
    }()
    
    lazy var noquarterState : NoquarterState = {
        return NoquarterState.init(machine: self)
    }()
    
    lazy var hasQuarterState : HasQuarterState = {
        return HasQuarterState.init(machine: self)
    }()
    
    lazy var soldState : SoldState = {
        return SoldState.init(machine: self)
    }()
 
    
    func releaseBall() {
        
        print("| NoquarterState |>>>>>" + #function)

        if count <= 0 {
            print("can't release ball")
        } else {
            count -= 1
            print("has release ball")
        }
        
    }
    
    
    init(count:Int) {
        
        self.count = count
        
        /*
        self.soldOutState = SoldOutState.init(machine: self)
        self.noquarterState = NoquarterState.init(machine: self)
        self.hasQuarterState = HasQuarterState.init(machine: self)
        self.soldState = SoldState.init(machine: self)
         */
        
        //self.state = self.soldOutState

    }
    
    func needInit() {
        
        self.state = self.noquarterState
    }
    
    func gumCount() -> Int {
        
        return count
        
    }
    
    
    
    
}

protocol MechineFunc { // 這個(gè)里面是機(jī)器所需要實(shí)現(xiàn)的方法 (接口)
    
    func InsertQuarter()
    func ejectQuarter()
    func turnCrank() //搬動(dòng)飲料機(jī)扳手
    func dispense()
    
}

class NoquarterState : MechineFunc {
    
    let gumballMachine:GumballMachine
    
    func InsertQuarter() {
        
        print("| NoquarterState |>>>>>" + #function)
        gumballMachine.state = gumballMachine.hasQuarterState // 修改狀態(tài)。
    }
    
    func ejectQuarter() {
        
        print("| NoquarterState |>>>>>" + #function)
        print("no quarter can't ejectQuarter")

    }
    
    func turnCrank() {

        print("| NoquarterState |>>>>>" + #function)
        print("no quarter can't turnCrank")
    }
    
    func dispense() {
        
        print("| NoquarterState |>>>>>" + #function)
        print("no quarter can't dispense")
    }
    
    required init(machine:GumballMachine) { //初始化的時(shí)候需要一個(gè)狀態(tài)機(jī)
        gumballMachine = machine
    }
}



class HasQuarterState : MechineFunc {
    
    let gumballMachine:GumballMachine
    
    required init(machine:GumballMachine) { //初始化的時(shí)候需要一個(gè)狀態(tài)機(jī)
        gumballMachine = machine
    }
    
    func InsertQuarter() {
        
        print("| HasQuarterState |>>>>>" + #function)
        print("has Quarter already")
        
    }
    
    func ejectQuarter() {
        
        print("| HasQuarterState |>>>>>" + #function)
        print("eject success change state to noquarterState ")
        gumballMachine.state = gumballMachine.noquarterState // 修改狀態(tài)。

    }
    
    func turnCrank() {
        
        print("| HasQuarterState |>>>>>" + #function)
        print("has sold")
        
        gumballMachine.state = gumballMachine.soldState // 修改狀態(tài)。

    }
    
    func dispense() {
        
        print("| HasQuarterState |>>>>>" + #function)
        print("no gumball dispensed")
        
    }
    
}


class SoldState : MechineFunc {
    
    let gumballMachine:GumballMachine
    
    required init(machine:GumballMachine) { //初始化的時(shí)候需要一個(gè)狀態(tài)機(jī)
        gumballMachine = machine
    }
    
    func InsertQuarter() {
        
        print("| SoldState |>>>>>" + #function)
        print("please wait already giving you a gumball")
        
    }
    
    func ejectQuarter() {
        
        print("| SoldState |>>>>>" + #function)
        print("sorry you has already turned the crank")
    }
    
    func turnCrank() {
        
        print("| SoldState |>>>>>" + #function)
        print("Turning twice doesn't get you another gumball")
    }
    
    func dispense() {
        
        print("| SoldState |>>>>>" + #function)

        gumballMachine.releaseBall() //釋放糖果
        
        if gumballMachine.gumCount() > 0 {

            gumballMachine.state = gumballMachine.noquarterState // 修改狀態(tài)。

        } else {
            
            gumballMachine.state = gumballMachine.soldOutState // 修改狀態(tài)。

        }
        
        
    }
    
}


class SoldOutState:MechineFunc {
    
    let gumballMachine:GumballMachine
    
    required init(machine:GumballMachine) { //初始化的時(shí)候需要一個(gè)狀態(tài)機(jī)
        gumballMachine = machine
        gumballMachine.state = gumballMachine.soldOutState //進(jìn)來以后直接就是沒貨了.
    }
    
    func InsertQuarter() {
        
        print("| SoldOutState |>>>>>" + #function)
        print(" no gumball soldout ")
        
    }
    
    func ejectQuarter() {
        
        print("| SoldOutState |>>>>>" + #function)
        print("sorry, you has no Quarter at all")
    }
    
    func turnCrank() {
        
        print("| SoldOutState |>>>>>" + #function)
        print("no gumball soldout")
    }
    
    func dispense() {
        
        print("no gumball soldout !")
        
    }

    
}


let m = GumballMachine.init(count: 4)

m.needInit() // 這個(gè)時(shí)候設(shè)置成了沒有 硬幣的狀態(tài)

m.turnCrank() // 這個(gè)時(shí)候如果發(fā)送的狀態(tài)不對(duì) 那么會(huì)輸出錯(cuò)誤信息 輸出 : no quarter can't turnCrank

m.insertQuarter() // 只有這個(gè)才是 沒有硬幣狀態(tài)可以處理的情況 添加硬幣 :insert success change state to hasQuarterState

m.ejectQUarter() // hasQuarterState有效的方法輸出 eject success change state to noquarterState

m.insertQuarter() // 再次添加硬幣編程

m.turnCrank()

m.turnCrank()

注意:其實(shí)狀態(tài)機(jī)模式和 策略模式有共通點(diǎn), 不過狀態(tài)機(jī)模式更加偏向于流程控制, 不讓類中有太多的if else判斷。 策略模式注重的是規(guī)避集成帶來的各種各樣的限制。

> 組合模式:

這里主要是針對(duì)我們常用的MVC模式的思考:
MVC的好處我就不再吧啦吧啦了。。

直接進(jìn)入主題:

1.V - C 這里面很好的體現(xiàn)了策略模式。 V只是關(guān)心如何去顯示好不在乎邏輯的處理。V相對(duì)C來說還可以說是實(shí)現(xiàn)了代理模式。V里面有相應(yīng)的變化委托給C進(jìn)行處理。

2.M - V 這里可以是一個(gè)觀察者模式,M會(huì)有不通的變化,V會(huì)根據(jù)M的變化進(jìn)行調(diào)整,同時(shí)一個(gè)M可以被多個(gè)C進(jìn)行觀察。

  1. V - V 相互的添加共存 可以說是一種組合模式 。

Tip:模式大概就先寫這么多了,書上還是有很多的模式介紹,例如:橋接模式等等。。模式那么多,如果你看完這篇文章了能在下次寫程序之前用幾分鐘想想該用什么模式你一定會(huì)有所提升,也就夠了。??

第二部分-總結(jié):

> 模式:

某種情景下 針對(duì)某中問題的解決方案.

1.情景:應(yīng)用某個(gè)模式的情況 這應(yīng)該會(huì)不斷的出現(xiàn)情況。

2.問題:想再某個(gè)情境下達(dá)到目標(biāo),也可以是某個(gè)情境下的約束。

3.解決方案:一個(gè)通用的設(shè)計(jì),用來解決約束,達(dá)到目的。

以上三點(diǎn)是書上寫的考慮現(xiàn)實(shí)問題時(shí)候設(shè)計(jì)模式需要注意的東西。

> 類型:

1.創(chuàng)建型->Singleton Factory
行為型-> Iterator command strategy
結(jié)構(gòu)型-> proxy

  1. 針對(duì)類的 針對(duì)對(duì)象的。

> 書上總結(jié):

1.設(shè)計(jì)模式是根據(jù)實(shí)際業(yè)務(wù)來選擇使用的,不應(yīng)該是先有一個(gè)模式再去將業(yè)務(wù)邏輯強(qiáng)行添加到模式中,不能本末倒置。

2.只有需要實(shí)踐擴(kuò)展的地方材質(zhì)的使用復(fù)雜性和模式。

> 自己總結(jié)的:

1.分析問題, 如果是面向?qū)ο蟮脑O(shè)計(jì),那么找到對(duì)象,對(duì)象中的變量等各個(gè)環(huán)間的相同點(diǎn)不通點(diǎn),相互之間的聯(lián)系。

2.面向問題
i.上一個(gè)對(duì)象的改變對(duì)本身的影響是什么。 尤其注意影響本身改變的條件個(gè)數(shù)。
ii.服務(wù)的對(duì)象是誰,需不需要檢測發(fā)送改變(數(shù)據(jù)) 后的狀態(tài)。

3.到底如何解除耦合,例如書中的遙控器的例子就是非常好的解除耦合的方案,選定一個(gè)模式之前需要思考一下流程和改變,這二者之間有沒有可能使用中間層等分離開來。

4.程序的后期是不是需要擴(kuò)展? 擴(kuò)展的是什么東西,如果是對(duì)象,應(yīng)該怎么設(shè)計(jì),如果是流程應(yīng)該怎么設(shè)計(jì)?宏觀的是這樣,微觀的也是這樣。

5.如果是 swift 去寫一個(gè)程序 如何使用的面向協(xié)議編程和泛型簡化設(shè)計(jì)模式中的一些繼承問題。

最后說明:

1.以前就算放鏈接大家還是要代碼,所以這次直接給出代碼了,導(dǎo)致篇幅較長,不過認(rèn)真對(duì)照寫一遍是不錯(cuò)的選擇,swift創(chuàng)建一個(gè)playground,直接復(fù)制粘貼就可以運(yùn)行,所有代碼都是我親自寫的并且經(jīng)過驗(yàn)證。

2.很多是個(gè)人的理解,不一定正確,看一本書也不可能100%吸收,如果有錯(cuò)誤或者理解的不同的地方,歡迎指出錯(cuò)誤,并且討論相關(guān)的模式。另外文章的側(cè)重點(diǎn)也不一樣,如果有補(bǔ)充的隨時(shí)歡迎。

3.本文其實(shí)應(yīng)該多一些思維導(dǎo)圖,奈何我是真的不太會(huì)用。如果有問題可以直接聯(lián)系我 QQ 645360439。

4.不管咋說放張圖:

性感迷人的模式小姐.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容