Swift重新整理Head First设计模式

引子:

为什么要运用设计模式?先有模式还是先有代码?MVC之间VC,MC他们之间的设计模式又是什么?一开始看到《Head First设计模式》写的东西就在想它有没有价值。本人目前从事客户端开发,这个博客分两个部分来说,第一个是讲讲各个模式,大概是书上模式的复刻版,基本上用swift重写了一遍。第二说说复合模式的相关理解和总结。这里面的代码部分其实不重要,重点看的是为什么这么写,如果真的想图文并茂的看这些东西,那么可以去看看书上,这本书写的非常好。如果想简单的获得设计模式的相关概括,那么我会尽量简洁并且保持一定的趣味和思考空间。另外原来的书是Java写的,本文是用的Swift,语法方面的东西我们暂时不做讨论。

(tip:这个后期我可能还会不停的添加相关的理解,先做一个说明,iOS 里面的NSCondition对应的生产者和消费者模式,有时间我回来整理一下。)

第一部分-模式介绍:

> 策略模式:

定义:定义了算法簇,分别封装起来,让他们可以相互替换,这个模式让算法的变化独立于使用算法的用户。

注解:有点蒙?我也是。再来看看原则:针对接口编程,而不是针对实现编程。如果你熟悉swift,没错就是针对协议编程。

先来想一个问题(书上的例子):
一个鸭子和一个飞机的共同点是什么?
1.会飞。2.有翅膀。OK,足够了。
如果你看到这里一定对面对对象有不少了解,那么一开始的代码可能是:

//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()

/*输出结果
 Duck Fly
 Plane Fly
 */

代码:问题搞定了,可是我们写代码的伟大事业难道不是为了节省下更多劳动力吗?确实如此,考虑一下这时候又有一个塑料鸭子,我们如何去复用上面的代码呢,塑料鸭子有翅膀,但是并不能自由飞翔。我们甚至不得不重写一个类来表示塑料鸭子,那么就是说我们感觉有共同点的东西并没有做到代码的复用。如何? 重写!

//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")
    }
    
}

//重点这里

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

代码写完了,我想说的是,在坐的各位........有什么感觉吗?我们把方法的规则用协议(Java里面叫接口)的方式重新写了一下,这样满足需要的就去实现这个协议。所以我们可以分解这个世界。。我们把那个活生生的鸭子也给分解了...并不是吃肉了,而是看做他是由翅膀、头、爪子等构成的物品,飞机也是,塑料鸭子也是,这样我们就可以分开去描述他们的组合方式。这样有再多的东西来了进行组合我们 三千东流水 取几瓢便是。这也是Swift里面主张的面向协议的核心思想。

原则:多用组合,少用继承。 (万年不变的当然还是用继承比较稳妥)

(另外swift里面的协议扩展是非常重要的! 后面如果可能还有一个关于swift 面向协议的文章分享,暂时放这里如果有我就回来修改添加上连接。另外下面的例子就不举反例了,知道模式非常有用就行。)

> 单例模式:

定义:确保一个类只有一个实例,并且提供一个全局的访问点。

注解:就是平时使用的各种Tool,通用方法等等。。

代码:

class MyManager  {
    static let shared = MyManager()
    private init() {} // 这个需要注意一下添加上。
}

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

在初始化类变量的时候,Apple 将会把这个初始化包装在一次 swift_once_block_invoke 中,以保证它的唯一性。不仅如此,对于所有的全局变量,Apple 都会在底层使用这个类似 dispatch_once 的方式来确保只以 lazy 的方式初始化一次。

另外,我们在这个类型中加入了一个私有的初始化方法,来覆盖默认的公开初始化方法,这让项目中的其他地方不能够通过 init 来生成自己的 MyManager 实例,也保证了类型单例的唯一性。如果你需要的是类似 default 的形式的单例 (也就是说这个类的使用者可以创建自己的实例) 的话,可以去掉这个私有的 init 方法。

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

> 观察者模式:

定义:定义了对象之间的一对多的依赖,这样当一个对象改变状态的时候他的所有依赖者都会接到通知并自动更新。 (Swift,OC开发者都应该很熟悉观察者模式了,对于OC,这里突然想到了防止崩溃的方法里面就一个是针对观察者的,后期整理。)

注解:这个和通知中心的概念其实差不多,都是写的订阅和发送的关系。这样设计的好处是对象之间的松耦合性,对象之间的依赖降到了最低。个人理解这个模式要避免使用过多,因为对象依赖是降低了,可是熵增加了,有点乱啊,大兄弟!

代码:系统是用相关的方法实现在NSObject里面的。不过这里自己实现一个加深一下印象吧。

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: ".")
    //}
}
// 观察者模式, 不能因为通知的顺序不同而产生额外的错误!! 每一个观察者是独立的。
class WeatherObserve {
    
    private var obsList:[CouldObserve] = [CouldObserve]() //创建一个待观察的列表。
    private var newTemp:String = "0"
    //var currentObserve:WeatherObserve?// = WeatherObserve() ----
    
    static let currentObserve = WeatherObserve() // 注意这里的 单例 写法。。
    private init () {}
    
    var changed = false
    
    static func getCurrentObserve() -> WeatherObserve { // 获取对应的 观察站
        return currentObserve // 这里需要返回一个单例形式的变量 单例上面的写法那个是有问题的。
    }
    
    func addObserve(new:CouldObserve) {
        self.obsList.append(new)
        
    }
    
    func setChanged() { // 增加一个容错机制。 添加上这个可以控制一下流程。
        
        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() { //发送通知。
        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"

/*
 输出结果 newMessage set
 */

估计苹果的底层也是这么个机制吧,不过一定没有这么简单。🤣 另外注意其中的单例写法别忘记了 private init() {}。

> 装饰着模式

定义:动态的将责任添加到对象要扩展的功能上,若要扩展功能,装饰者提供了比集成更有弹性的代替方案。

注释:书上这种模式可以用在大面积的零散部件(类型相同),的情况加做挑挑拣拣的累加处理。例如一个 一杯咖啡的设计, 里面添加的作料和咖啡本身都是可供销售的商品,我们需要的是一个对象能装点所有的东西,然后一起计算价格。

代码:

import UIKit

var str = "Hello, playground"


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

class Good { //这个作为一个父类 内容是基本上不会变化的 所以没什么必要进行抽离使用继承.
    
    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
        }
    }
}

//作料
/*
    这里写的两步倒是挺麻烦的。。因为swift不能在 protocol extension 或者是 继承的时候继承存储属性,所以只能通过一个协议写一个限制,保证类实现这个价格,然后通过计算属性对这个价格进行一个返回的操作。这个不支持继承存储感觉有点傻,但是没办法~ 如果有好方法请告诉我。~
 */
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 }

}


//现在来一份添加 牛奶和糖的猫屎咖啡

var coffee:Good = MaoShiCoffee() //这里面 必须要规定一下 是Good类型要不下面就类型出错了。
coffee.totalPrice //10
coffee = Milk.init(goods: coffee)
coffee.totalPrice //12
coffee = Suger.init(goods: coffee)
coffee.totalPrice //15

注意:
留意看其中的注释,因为swift的一些类型限制,还有继承的一些限制,所以代码看着有点重复,也没啥好办法,如果有,请留言🤔。

> 工厂方法:

定义:了一个创建对象的接口,但是由于子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

原则:依赖倒置原则--> 要依赖抽象,不要依赖具体的类。

注释:工厂方法,以前总感觉没什么用,好像就是创建一个UIButton这样简单的功能,我也问过自己为什么不用一个switch直接搞定得了???后来发现了工厂方法还分为简单工厂和抽象工厂。利用上述的原则,我们分析一个问题要从最后的结果到最前面的分析过程,拿买饼这样一个例子来说,有不同的饼店,不同的饼店里面可能有相同名字的饼,比方说多个店里面都有“酱香饼”,我们可以去 “大望路店”去买,也可以去“中关村店”去买,而这两个店铺又可以去不同的工厂订购需要的饼,拿回来的饼又可以进行二次的加工,比方说可以打包或者添加佐料。OK,有点绕晕了。我也是啊,兄弟!来吃点药,不要晕。。。

分析:如果没有学习工厂方式的设计原则,那么我们处理这个问题的方式应该是从工厂写起来,有了工厂然后创建的对象传递给店铺店铺加工之后再给顾客。。。这么写没错,可是店铺可以增加减少倒闭,工厂也不一定是一个,甚至加工方式佐料都可能是后期添加减少的选项,拿到手的东西经过这一套的处理,估计很难对整个系统扩展。。因为我们依赖的原则是最后生产出来的产品。。(什么?你压根就没有考虑这个问题?。。) 现在反过来,我们拿到的是商品对象,所以要有店铺, 商品和店铺上游的工厂 是没有任何关系的,一个店铺要有生产好多种商品的能力,也不可能仅仅从一个厂商去进购商品。

我们大概需要一个这样的购买方式:


购买饼的方式.png

不同的店铺去买不同的pizza(饼),给出的饼里面可能会有不同的作料(例如 shit)的添加。。。

我们看看店铺的方法:

Store.png

这里使用协议规范了开店的需求,然后通过order方法可以进行分发操作,同时调用本身的creat 来进行制作,因为factory也是满足了各种规范也就能愉快的执行creat中的三种操作了。

分析一下,这个store是不和factory有任何耦合性的,store可以个性化的定制自己需要的factory,不同的店铺由自己的内部决定。

再来看看Pizza(就是我们说的饼店里卖的饼)的结构。


pizza--->就当做我们的饼.png

这里没有什么特别的就是一些限定方法,实现的类里面给出自己独特的做法(北京的饼可能就添加了shit,其他的就没有这个服务。)

再看看factory。


factory.png

到了这里 回头再看看,我们买饼的过程,stroe 和 factory 是分开的 factory 和 pizza也是分开的。三者之间除了调用上的联系之外没有任何联系了。所以是解耦的。其实我个人理解设计模式就是用来解耦合的,这样才能易于扩展,所以不要指望外包的代码写的很规范~~~~

完整代码如下:

import UIKit

// 我要做馅饼, 现在要由工厂来生产 , 工厂可以扩展, 馅饼可以增加, 馅饼店可以订购不同的商品, 馅饼也是可以更换材料和做法,

// 倒置依赖原则


protocol Amatore {
    var price:String{get}
}

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

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


/****馅饼****/
protocol Pizza { //我们的馅饼 不一定是什么地方的 但是规定一个有 基础价格和添加方法
    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 -> 扩展了
    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 停业了~
    
    static func creat(fac: Company) -> Pizza? {
        return nil
    }
    
    static func order(name :String) -> Pizza? {
        
        return nil
    }
}
// 酱香
let bingS = StoreDawanglu.order(name: "酱香") //创建
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()

总结一下:我们做的这些工作都是为了解除耦合性,增加可读性,维护性。上面这个过程可以简答理解为需求倒置,从下至上的原则,形状是一个倒三角。。其实我并没有做过大型的后台,对这个理解的层次不是那么深刻,如果有高手看到请留言告诉我,我一定加你好好请教。。

> 命令模式

定义:将请求封装成对象,以便使用不同的请求,队列或日志来参数化对象。命令模式一般支持撤销的操作。

注释:这个模式可以想象成一个遥控器,只是负责开关的操作,遥控器本身并不知道具体的工作是什么。也可以理解为餐厅里面的传餐员,他并不知道厨师要做什么,只是把单子上的餐品名称传递给了后厨。这个方式和后面要写的状态模式有些类似。不过并不是完全一样。

下面就拿一个遥控器的例子来说吧:

首先看看要被控制的东西是一个空调简单称为Air:


Air牌子的空调.png

这个是实际空调的执行方法,里面可以弄各种各样的操作,不过规定(protocol)中需要有的方法是 on off unknow unDo, 这四个方法。

遥控器.png

遥控器中有一个elementCommands这个是告诉读者可以这样去限定遥控器的按键数量,这里直接使用了一个elementCommand 代表着有两个东西需要控制,一个0号按键是Air对应的空调,一个1号按键light对应的灯。lastCommand记录一下用来做撤销操作。其他的实现方法应该就一目了然了。

然后就可以通过RemoteControl.shared.electicalOnNumber(index: 1) 这样的遥控器单例进行操作对应的按键操作了。

完整代码:

import UIKit

var str = "Hello, playground"


// 写一下 order 模式  


// 写一个家居的模式,


// 两个 电气

protocol Electrical {
    
}

class AirConditioner : Electrical {
    
}

class Lighting : Electrical {
    
}


protocol CommandProtocol { //规定这个家用电器的状态
    
    associatedtype E:Electrical
    
  
}

protocol CommandFunc { // 规定
    
    func on() -> CommandFunc
    func off() -> CommandFunc
    func unknow() -> CommandFunc
    func unDo() -> CommandFunc
    
}
// 要知道程序的世界里面部分男女老少 没有贫贱之分, 所以 被人使用了千百遍的代码 和新写出而来的代码 在使用平等性上几乎一样。
// 这地方注意类型
class CommandAir:CommandProtocol,CommandFunc {
    
    //假定这个是空调遥控器
    typealias E = AirConditioner
    
    var currentStatus:String = "unKnow"
    var lastStatus:String = "unKnow"
    
    
    func unknow() -> CommandFunc {
        print("空调不知道什么状态")
        return self
    }

    func off() -> CommandFunc {
        print("空调关了啊啊啊")
        return self
    }

    func on() -> CommandFunc {
        print("空调开了")
        return self
    }
    
    func unDo() -> CommandFunc {
        print("撤销操作")
        return self
    }

}

class CommandLight:CommandProtocol,CommandFunc {
    
    typealias E = Lighting
    
    func unknow() -> CommandFunc {
        print("灯不知道什么状态")
        return self
    }
    
    func off() -> CommandFunc {
        print("灯关了啊啊啊")
        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
    }
    
}

// 这里是一个遥控器

class RemoteControl {
    
    // 遥控器设置成单例
    static let shared:RemoteControl = RemoteControl()
    
    private init () {}
    
    // 假设现在遥控器上面有两个按钮是开, 两个按钮对应的是是关。 一个按钮是取消。
    var elementCommand : [CommandFunc] = [CommandAir(),CommandLight()]
    
    var elementCommands = Array.init(repeating: CommandFunc.self, count: 6) //设置最多按键数量
    
    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) { // 功能/对应的位置
        elementCommand.replaceSubrange(Range.init(uncheckedBounds: (index,1)), with: [element]) //从index开始的第几个元素
        print(elementCommand)
    }
    
}



// 现在添加一个插槽的操作


RemoteControl.shared.elementInsert(element: CommandLight(), index: 1) //实现了一个插座的功能可以增加元素。

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)

总结:命令模式 旨在命令和实现是完全解耦的, 控制的一方只是通知其中保存的对象执行相应的动作,至于成功失败操作过程是不关心的。这种上层的逻辑如果在swift中可以对几个页面发送通知,或者是一个页面的不同控件做对应的操作。如果按照流程进行一波操作就和状态模式有点像了,状态模式的时候再说。

> 适配器模式

定义:将一个类的接口转换为客户期望的另一个接口,适配器让原本接口不兼容的类可以相互合作。

注释:这个好像就是说 两个类之间写一个中间层,输入和转换对应的参数,让程序正常运行。这个模式和装饰器模式的对比
装饰器:不改接口但是加入责任。
适配器:将一个接口封装成另一个接口。

这个模式我没有好的例子,书上看的也有点不透彻,暂时先放这里。

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

模板方法:抽离主要步骤,具体类具体实现。
钩子方法:实现一些空的返回,或者是Bool这些简单的类型返回,如果子类继承了可以重写相关方法实现对流程的控制。这种个人感觉有点类似于子类对父类的一些流程控制。


流程控制.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:这个上面的代码虽然简单的很,我甚至都不想写。不过如果这个步骤是一个初始化过程,类里面有很多变量需要初始化的时候呢?有的是空有的是可选类型的时候呢。。如果类似于一个控件上面需要隐藏和显示某些颜色等状态呢?。。

> 状态模式

定义:允许对象在内部状态改变的时候改变它的行为,对象看起来好像修改了它的类。

注释:这个类型和我有点渊源,所以这里面有额外的注意。这个模式其实个人感觉是应对多输入时候的解决方案,比方说一个糖果售卖机在执行一条指令的时候,你如果随便去乱按的话,大部分按键是没有作用的,因为屏蔽掉了,不过关机这样的特殊按键还是会起作用,所以针对不同时刻的多种输入状态所发生的动作处理过程,挺适合用这个状态模式的。(注意命令模式和这个的区别,命令不会涉及到任何的执行过程,而这个状态模式是自己去维护状态在不同时刻的状态。有点相同的是 二者的控制部分都不需要参与具体的逻辑实施。)

下面以一个弹球游戏机GumballGame作为例子,大概就是投币进去,然后一转到指定位置就可以给出一个礼物(别告诉我你没有玩过这种坑爹的游戏?如果有就想想一下这种机器就在你眼前。)。机器可以与外界的交互有 投入钱币,退出钱币,转动手柄。

所以写一个机器,我们把这些方法都实现进来:

GumballGame.png

再想一下机器可能处在的状态可能是什么样的呢?

1.卖光了(没有球可以出来了,虽然不太可能)
2.没投币
3.投币了
4.正在旋转罗盘

然后就结束了。。。
这里面我们需要考虑一下,例如,正在旋转的时候我选择了退币,这个时候已经不能退币了,但是如果在投币状态下是可以的,所以我们说这个退币的动作在GumballGame旋转的时候是无效的,在GumballGame投入钱币后是有效的。根据这个例子来看,这种状态一旦是多起来,我们需要些多少个 if else 才能把所有的情况都表示完?2的...次方??不知道是不是正解的理解:状态机模式可以有效的去掉程序里面 if else 的数量,让每一个状态下面都有独立的判断。这么说来,每个状态都是一个独立的对象去处理相关的逻辑了...

给出状态的代码:


State.png

protocol 规定出每个状态对应的方法,各个state去实现每个状态。
这个状态和状态机实现的状态方法流程上是一样的,每一个单独的状态里面都具有独立处理所有情况的能力。。。这里没有写成一样的为了看的更加清晰一点。

特别注意一下这里面的require 修饰的init方法。这里要求包含一个状态机,然后上面的方法中有效的是InsertQuarter()执行了就会通过状态机去更改成下一个状态,所以,我们不要错误的理解为状态机控制了状态,而是状态会自己有一个流程去不停的告诉状态机, 状态机接收到外界输入后再去调用状态执行相关的操作。有点绕?是的,没关系。先记住这里改变状态的方式是通过状态机去切换下一个状态。

再回到状态机里面:

State初始化.png

状态机里面包含了各个状态。等待被切换。再来看看状态的切换过程你是不是没有注意到初始化状态机时候的那个图片中包含了如下:


状态机切换的过程.png

注意,这里面的state可不一定是同一个state 啊,可能是初始化过的任何一个状态,当调用了各自有效的方法之后进行了改变之后的state,如果这里不明白了可以在看看上面state的定义和转换。

另外一个全局的状态变量这个就不多说了:


全局状态.png

下面是调用方式:


调用方式.png

总结:
让state去决定下一步应该做什么。 每一个state 里面又有自己的独特处理方式,(满足一整套的流程 纵向流程), 自己处理完成之后可以通过自身持有的 machine 对象主动切换调用下一个状态。 让原本需要大量 if else 的判断逻辑成为主动的流程性的东西。通过这个流程控制清晰的展现出了各个环节的内容 不同状态处理相关方法会得到不同的输出结果。非常方便的就可以添加上更多的状态。 另外可以在自己各自的状态中进行事件的处理和分发。

代码:

import UIKit


// 这里测试一下状态机模式。

/*
enum State:Int { //这里是状态标志位。
    
    case  SOLD_OUT = 1
    case  NO_Quarter
    case  Has_Quarter
    case  SOLD
    
}
*/
/* 这样搞还不太行??
enum State:MechineFunc { //这里是状态标志位。
    
    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
    
    // 这个地方一定会出问题的, 因为后面 get 里面调用了self.state 这个会出现一个无限循环
    /*
    var state : MechineFunc? {
        
        set {
            self.state = newValue
        }
        get {
            print("get state")
            if self.state == nil { print("self.state == nil") ; return soldOutState } //如果这个是nil 那么就返回卖光了
            print("what state")
            return self.state
        }
    } //默认给一个卖光了
    */
    var state : MechineFunc? {
        didSet {
            print("=============> current status \(state!) ")
        }
    }
    /*
    var soldOutState:MechineFunc
    var noquarterState:MechineFunc
    var hasQuarterState:MechineFunc
    var soldState:MechineFunc
    */
    /* 
        这样形成了 一个状态模式, 这里面的模式都是可以提供给这个状态机进行增加和删除的。
     */
    
    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 { // 这个里面是机器所需要实现的方法 (接口)
    
    func InsertQuarter()
    func ejectQuarter()
    func turnCrank() //搬动饮料机扳手
    func dispense()
    
}

class NoquarterState : MechineFunc {
    
    let gumballMachine:GumballMachine
    
    func InsertQuarter() {
        
        print("| NoquarterState |>>>>>" + #function)
        gumballMachine.state = gumballMachine.hasQuarterState // 修改状态。
    }
    
    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) { //初始化的时候需要一个状态机
        gumballMachine = machine
    }
}



class HasQuarterState : MechineFunc {
    
    let gumballMachine:GumballMachine
    
    required init(machine:GumballMachine) { //初始化的时候需要一个状态机
        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 // 修改状态。

    }
    
    func turnCrank() {
        
        print("| HasQuarterState |>>>>>" + #function)
        print("has sold")
        
        gumballMachine.state = gumballMachine.soldState // 修改状态。

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


class SoldState : MechineFunc {
    
    let gumballMachine:GumballMachine
    
    required init(machine:GumballMachine) { //初始化的时候需要一个状态机
        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 // 修改状态。

        } else {
            
            gumballMachine.state = gumballMachine.soldOutState // 修改状态。

        }
        
        
    }
    
}


class SoldOutState:MechineFunc {
    
    let gumballMachine:GumballMachine
    
    required init(machine:GumballMachine) { //初始化的时候需要一个状态机
        gumballMachine = machine
        gumballMachine.state = gumballMachine.soldOutState //进来以后直接就是没货了.
    }
    
    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() // 这个时候设置成了没有 硬币的状态

m.turnCrank() // 这个时候如果发送的状态不对 那么会输出错误信息 输出 : no quarter can't turnCrank

m.insertQuarter() // 只有这个才是 没有硬币状态可以处理的情况 添加硬币 :insert success change state to hasQuarterState

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

m.insertQuarter() // 再次添加硬币编程

m.turnCrank()

m.turnCrank()

注意:其实状态机模式和 策略模式有共通点, 不过状态机模式更加偏向于流程控制, 不让类中有太多的if else判断。 策略模式注重的是规避集成带来的各种各样的限制。

> 组合模式:

这里主要是针对我们常用的MVC模式的思考:
MVC的好处我就不再吧啦吧啦了。。

直接进入主题:

1.V - C 这里面很好的体现了策略模式。 V只是关心如何去显示好不在乎逻辑的处理。V相对C来说还可以说是实现了代理模式。V里面有相应的变化委托给C进行处理。

2.M - V 这里可以是一个观察者模式,M会有不通的变化,V会根据M的变化进行调整,同时一个M可以被多个C进行观察。

  1. V - V 相互的添加共存 可以说是一种组合模式 。

Tip:模式大概就先写这么多了,书上还是有很多的模式介绍,例如:桥接模式等等。。模式那么多,如果你看完这篇文章了能在下次写程序之前用几分钟想想该用什么模式你一定会有所提升,也就够了。🤣

第二部分-总结:

> 模式:

某种情景下 针对某中问题的解决方案.

1.情景:应用某个模式的情况 这应该会不断的出现情况。

2.问题:想再某个情境下达到目标,也可以是某个情境下的约束。

3.解决方案:一个通用的设计,用来解决约束,达到目的。

以上三点是书上写的考虑现实问题时候设计模式需要注意的东西。

> 类型:

1.创建型->Singleton Factory
行为型-> Iterator command strategy
结构型-> proxy

  1. 针对类的 针对对象的。

> 书上总结:

1.设计模式是根据实际业务来选择使用的,不应该是先有一个模式再去将业务逻辑强行添加到模式中,不能本末倒置。

2.只有需要实践扩展的地方材质的使用复杂性和模式。

> 自己总结的:

1.分析问题, 如果是面向对象的设计,那么找到对象,对象中的变量等各个环间的相同点不通点,相互之间的联系。

2.面向问题
i.上一个对象的改变对本身的影响是什么。 尤其注意影响本身改变的条件个数。
ii.服务的对象是谁,需不需要检测发送改变(数据) 后的状态。

3.到底如何解除耦合,例如书中的遥控器的例子就是非常好的解除耦合的方案,选定一个模式之前需要思考一下流程和改变,这二者之间有没有可能使用中间层等分离开来。

4.程序的后期是不是需要扩展? 扩展的是什么东西,如果是对象,应该怎么设计,如果是流程应该怎么设计?宏观的是这样,微观的也是这样。

5.如果是 swift 去写一个程序 如何使用的面向协议编程和泛型简化设计模式中的一些继承问题。

最后说明:

1.以前就算放链接大家还是要代码,所以这次直接给出代码了,导致篇幅较长,不过认真对照写一遍是不错的选择,swift创建一个playground,直接复制粘贴就可以运行,所有代码都是我亲自写的并且经过验证。

2.很多是个人的理解,不一定正确,看一本书也不可能100%吸收,如果有错误或者理解的不同的地方,欢迎指出错误,并且讨论相关的模式。另外文章的侧重点也不一样,如果有补充的随时欢迎。

3.本文其实应该多一些思维导图,奈何我是真的不太会用。如果有问题可以直接联系我 QQ 645360439。

4.不管咋说放张图:

性感迷人的模式小姐.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342