美文网首页架构/设计
独孤九剑--设计模式(iOS行为型篇)

独孤九剑--设计模式(iOS行为型篇)

作者: _小沫 | 来源:发表于2023-03-07 17:24 被阅读0次

    独孤九剑--设计模式(iOS创建型篇)
    独孤九剑--设计模式(iOS结构型篇)

    观察者模式(Observer)

    定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

    UML类图

    observer.png
    • Subject:抽象主题角色,把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。
    • ConcreteSubject:具体主题角色,在内部状态改变时,给所有注册过的观察者发出通知。
    • Observer:抽象观察者角色,为所有的观察者定义一个接口,在得到主题的通知更新自己。
    • ConcreteObserver:具体观察者角色,该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态和主题的状态相协调。

    iOS系统中的应用

    iOS中NSNotificationCenter、KVO都是观察者模式的运用

    示例代码

    标准格式的观察者代码,详见demo中/自定义观察者/部分;
    这里仿写一下系统NotificationCenter的简单版本,省去抽象角色Subject、ConcreteSubject,实现一个观察者功能;

    1. 新建通知中心单例
    // ConcreteSubject
    class NotificationCenter {
        static let `default` = NotificationCenter()
        
        private init() {}
     }
    
    1. NotificationCenter中定义观察者集合
    // NotificationCenter可以添加不同name的监听,这里直接使用字典记录每个name下所有的观察者
    var observers: [String : [Observation]] = [:]
    
    1. 封装观察的信息
    // ConcreteObserver
    class Notification: NSObject {
        let name: String
        var userInfo: [String : Any]?
        
        init(name: String, userInfo: [String : Any]? = nil) {
            self.name = name
            self.userInfo = userInfo
        }
    }
    
    1. 定义观察者封装类
    // 封装观察者对象、实现的selector; 
    class Observation {
        // 封装观察者对象、实现的selector;
        let observer: Any
        let selector: Selector
        
        init(observer: Any, selector: Selector) {
            self.observer = observer
            self.selector = selector
        }
        
        // 响应通知
        func update(notification: Notification) {
            guard let aClass = observer as? NSObjectProtocol else { return }
            if aClass.responds(to: selector) {
                aClass.perform(selector, with: notification)
            }
        }
    }
    
    1. NotificationCenter实现,添加、删除观察者、发送通知的功能
    func add(observer: Any, selector: Selector, name: String) {
        let observation = Observation(observer: observer, selector: selector)
        if var obs = observers[name] {
            obs.append(observation)
            observers[name] = obs
        } else {
            let obs = [observation]
            observers[name] = obs
        }
    }
    func remove(_ observer: Any, name: String) {
        guard var observers = observers[name] else { return }
        
        for (index, observation) in observers.enumerated() {
            if let aClass = observation.observer as? NSObjectProtocol, let bClass = observer as? NSObjectProtocol  {
                if aClass.isEqual(bClass) {
                    observers.remove(at: index)
                    self.observers[name] = observers
                    break
                }
            }
        }
    }
    func post(name: String, userInfo: [String : Any]? = nil) {
        let notification = Notification(name: name, userInfo: userInfo)
        post(notification: notification)
    }
    func post(notification: Notification) {
        guard let observers = observers[notification.name] else { return }
        for observation in observers {
            observation.update(notification: notification)
        }
    }
    

    一个简易版通知中心使用

    NotificationCenter.default.add(observer: self, selector: #selector(notice(n:)), name: "test")
    NotificationCenter.default.add(observer: PatternTest(), selector: #selector(notice(n:)), name: "test")
    NotificationCenter.default.remove(self, name: "test")
    NotificationCenter.default.post(name: "test")
    

    ps:系统库中的NotificationCenter实现远比这个复杂,他需要兼容处理各种情况,但本质原理基本是这样的;
    如对NotificationCenter系统实现有兴趣的可以参考:
    NSNotificationCenter底层探究

    中介者模式(Mediator)

    用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互

    UML类图

    mediator.png
    • Mediator:抽象中介者,定义了同事对象到中介者对象之间的接口。
    • ConcreteMediator:具体中介者,实现抽象中介者的方法,需要从具体的同事类那里接收消息,并且向具体的同事类发送信息。
    • Colleague:抽象同事类
    • ConcreteColleague:具体同事类,需要知道自己的行为即可,但是它们都需要认识中介者

    示例

    有3个控制器A,B,C,A,B,C三者之间都能相互跳转;
    简单的代码实现:A,B,C内部均引用、耦合其他控制器;

    // ViewControllerA
     switch random {
     case 0:
         self.present(ViewControllerB(), animated: true)
     default:
         self.present(ViewControllerC(), animated: true)
     }
    
    // ViewControllerB
     switch random {
     case 0:
         self.present(ViewControllerA(), animated: true)
     default:
         self.present(ViewControllerC(), animated: true)
     }
    
    // ViewControllerC
     switch random {
     case 0:
         self.present(ViewControllerA(), animated: true)
     default:
         self.present(ViewControllerB(), animated: true)
     }
    

    它们间的关系如下:


    可以看到,这还是只有3个类的情况,最多就已经有6种关系;按照无向图2个节点的关系数量计算,如果有n个对象,那就会有(n-1)xn种关系;
    如果系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象;

    使用中介者模式优化

    1. 抽象中介者协议
    // Mediator
    protocol RouterMediator {
        var viewControllers: [String : ViewControllerSubject] { get }
    
        mutating func register(vc: ViewControllerSubject, path: String)
        func router(from: ViewControllerSubject, to path: String)
    }
    
    1. 实现具体路由中介
    // ConcreteMediator
    struct RouterStructure: RouterMediator {
        var viewControllers: [String : ViewControllerSubject]
        
        // 通过注册的方式 Mediator保存所有的ConcreteColleague
        mutating func register(vc: ViewControllerSubject, path: String) {
            viewControllers[path] = vc
        }
        
        func router(from: ViewControllerSubject, to path: String) {
            if let toVc = viewControllers[path] {
                from.present(toVc, animated: true)
            }
        }
    }
    
    1. 抽象路由的Controller类,提供mediator引用
    // Colleague
    class ViewControllerSubject: UIViewController {
        var router: RouterMediator?
    }
    
    1. 具体controller实现,所有跳转逻辑交由mediator实现
    switch random {
    case 0:
        self.router?.router(from: self, to: "/A")
    default:
        self.router?.router(from: self, to: "/C")
    }
    
    // 其他vc代码类似
    

    使用:

    var mediator = RouterStructure()
    let a = ViewControllerA()
    a.router = mediator
    mediator.register(vc: a, path: "/A")
    ....
    

    关系图:

    以上只是基于中介者模式的最基本代码实现,还有很多优化空间;比如完全没必要新建ViewControllerSubject类,可以直接使用extension实现;还有每次都需要实现注册代码,比较繁琐也容易出错,也完全可以使用动态特性、硬编码实现,通过类名字符串得到对应具体对象;
    在中介者模式的基础上,可以实现一套完整的组件化框架;CTMediator就是一个很好的例子;

    策略模式(Strategy)

    定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化

    UML类图

    strategy.png
    • Context:环境角色,持有一个策略类的引用,最终给客户端调用
    • Strategy:抽象策略角色,通常由一个接口或者抽象类实现
    • ConcreteStrategy:具体策略角色,包装了相关的算法和行为

    示例

    以一个将2个数加减乘的功能为例
    (为了演示方便,只使用最简单的算法;实际上如果真的只是加减这么简单就完成不用策略了;这些算法可以联想成购物时不同的折扣算法)

    • 简单实现
    func operate<T: Numeric>(a: T, b: T) -> T {
        switch type {
        case .add:
            return a + b
        case .sub:
            return a - b
        case .mul:
            return a * b
        }
    }
    

    把一堆算法塞到同一段代码中,然后使用一系列的if else 或switch case来决定哪个算法;
    可以把相关算法分离成不同的类,成为策略。

    • 策略模式
    1. 抽象策略协议,提供算法接口
    protocol Strategy {
        associatedtype T
    
        func algorithm(a: T, b: T) -> T
    }
    
    1. 创建具体策略类,实现对应算法
    struct AddStrategy<T: Numeric> : Strategy {
        func algorithm(a: T, b: T) -> T {
            return a + b
        }
    
    }
    
    struct SubStrategy<T: Numeric> : Strategy {
        func algorithm(a: T, b: T) -> T {
            return a - b
        }
    
    }
    
    struct MulStrategy<T: Numeric> : Strategy {
        func algorithm(a: T, b: T) -> T {
            return a * b
        }
    
    }
    
    1. 创建context类,使用策略
    struct STContext<S: Strategy> {
        var strategy: S
        
        func operate(a: S.T, b: S.T) -> S.T {
            return strategy.algorithm(a: a, b: b)
        }
    }
    

    使用:

    let strategy1 = AddStrategy<Int>()
    let strategy2 = MulStrategy<Double>()
    let context1 = STContext(strategy: strategy1)
    print(context1.operate(a: 1, b: 2))
    let context2 = STContext(strategy: strategy2)
    print(context2.operate(a: 1.1, b: 2.1))
    

    策略模式和简单工厂模式的区别

    从UML图看,策略模式和简单工厂模式非常相似;
    简单工厂模式是创建型的模式,它接受类型指令创建符合要求的产品实例;而策略模式是行为型的模式,它接受已经创建好的算法实例,实现不同的行为。

    状态模式(State)

    允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

    UML类图

    state.png
    • Context:环境角色,定义操作接口,维护不同状态。
    • State:抽象状态角色,定义状态接口。
    • ConcreteState:具体状态角色,实现Context的一个状态所对应的行为。

    示例

    以线上定房为例,房间的状态有:空闲、已预订、已入住。空闲房间的状态可以转变为:已预订、已入住。已预订状态房间的状态可以转变为:已入住、空闲。已入住房间的状态可以转变为:空闲。

    1. 抽象房间状态,定义接口
    // state
    protocol RoomState {
        func book() -> Bool
        func checkIn() -> Bool
        func checkOut() -> Bool
    }
    
    1. 创建具体房间状态类,实现对应方法
    
    // concreteState
    class FreeState : RoomState {
        func book() -> Bool {
            print("预约成功")
            return true
        }
        
        func checkIn() -> Bool {
            print("未预约")
            return false
        }
        
        func checkOut() -> Bool {
            print("未预约")
            return false
        }
    }
    
    class CheckInState : RoomState {
        func book() -> Bool {
            print("已入住,不能预约")
            return false
        }
        
        func checkIn() -> Bool {
            print("已有入住,不能再入住")
            return false
        }
        
        func checkOut() -> Bool {
            print("退房成功")
            return true
        }
    }
    
    class BookState : RoomState {
        func book() -> Bool {
            print("已有预约,不能再预约")
            return false
        }
        
        func checkIn() -> Bool {
            print("入住成功")
            return true
        }
        
        func checkOut() -> Bool {
            print("取消预约成功")
            return true
        }
    }
    
    1. 创建房间环境类,定义实现操作接口,维护状态逻辑
    // context
    struct Room {
        // 所有状态
        let freeState = FreeState()
        let checkInState = CheckInState()
        let bookState = BookState()
        
        // 当前状态
        var state: RoomState
        
        init() {
            state = freeState
        }
        
        mutating func book() {
            // 预约
            let result = state.book()
            if result {
                // 预约成功 状态变更为已预约
                state = bookState
            }
        }
        
        mutating func checkIn() {
            // 入住
            let result = state.checkIn()
            if result {
                // 入住成功 状态变更为已入住
                state = checkInState
            }
        }
        
        mutating func checkOut() {
            // 退房
            let result = state.checkOut()
            if result {
                // 退房成功 状态变更为空闲
                state = freeState
            }
        }
    }
    

    使用:

    var room = Room()
    room.book()
    room.book()
    room.checkIn()
    
    /*预约成功
    已有预约,不能再预约
    入住成功
    */
    

    适用场景

    • 对象的行为依赖于它的状态,并且可以根据它的状态而改变它的相关行为
    • 代码中包含大量与对象状态相关的条件语句

    状态模式和策略模式的区别

    状态模式和策略模式也非常相似,都是由context来选择、调用具体的行为;
    区别在于策略模式context角色,只引用并使用具体的某个strategy类,不同的strategy类没有转接关系;状态模式context角色,引用并使用了所有的state类,并在内部维护、切换不同的state,不同的state直接存在着转接关系;

    命令模式(Command)

    将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

    UML类图

    command.png
    • Command:抽象命令角色,声明一个给所有命令类的抽象接口。
    • ConcreteCommand:具体命令角色,定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。
    • Invoker:请求者角色,负责调用命令对象执行请求。
    • Receiver:接收者角色,负责具体实施和执行一个请求。

    示例

    编写一个电视遥控功能,不同的按键处理不同的操作;

    1. 定义receiver电视类,支持打开、关闭、切换频道功能
    // receiver
    struct TV {
        func open() {
            print("tv open")
        }
        
        func close() {
            print("tv close")
        }
        
        func `switch`() {
            print("channel switch")
        }
    }
    
    1. 定义抽象命令及具体命令
      每个具体命令对应不同的操作,内部实际是调用receiver处理;在具体命令执行时可以写入日志,实现记录请求日志
    // command
    protocol TVCommand {
        var tv: TV { get }
        
        func execute()
    }
    
    // concreteCommand
    struct TVOpenCommand : TVCommand {
        var tv: TV
        
        func execute() {
            tv.open()
            
            // 记录请求日志
            print("执行了open命令")
        }
    }
    
    struct TVCloseCommand : TVCommand {
        var tv: TV
        
        func execute() {
            tv.close()
            
            // 记录请求日志
            print("执行了close命令")
        }
    }
    
    struct SwitchCommand : TVCommand {
        var tv: TV
        
        func execute() {
            tv.switch()
            
            // 记录请求日志
            print("执行了switch命令")
        }
    }
    
    1. 定义Invoker命令执行类
    • 通过字典存储所有要执行的command,字典的key映射具体的command,实现用不同的请求对客户进行参数化
    • 记录所有command执行顺序,实现对请求排队
    • 支持删除command,模拟实现撤销功能;实际开发是可以使用NSUndoManager等实现撤销与恢复功能
    // invoker
    struct TVInvoker {
        var commandMaps: [String : TVCommand] = [:] // 存储所有command (对不同的请求进行参数化)
        var keys: [String] = [] // 记录command顺序
        
        mutating func addCommand(_ cm: TVCommand, for key: String) {
            keys.append(key)
            commandMaps[key] = cm
        }
        
        mutating func removeCommand(for key: String) {
            keys.removeAll(where: { $0 == key })
            commandMaps.removeValue(forKey: key)
        }
        
        func invoke(key: String) {
            if let command = commandMaps[key] {
                command.execute()
            }
        }
        
        func invoke() {
            // 按顺序执行 (对请求排队)
            for key in keys {
                invoke(key: key)
            }
        }
    }
    

    测试:

    let receiver = TV()
    let commandOpen = TVOpenCommand(tv: receiver)
    let commandClose = TVCloseCommand(tv: receiver)
    let commandSwitch = SwitchCommand(tv: receiver)
    var invoker2 = TVInvoker()
    invoker2.addCommand(commandOpen, for: "o")
    invoker2.addCommand(commandSwitch, for: "s")
    invoker2.addCommand(commandClose, for: "c")
    //  invoker2.invoke(key: "o")
    //  invoker2.invoke(key: "c")
    invoker2.removeCommand(for: "s")
    invoker2.invoke()
    
    /*tv open
    执行了open命令
    tv close
    执行了close命令
    */
    

    适用场景

    • 应用程序需要支持撤销与恢复功能
    • 需要在不同的时间指定请求、将请求排队
    • 用对象参数化一个动作以执行操作
    • 记录修改日志,在系统故障时能重做一遍

    命令模式在iOS系统中的使用

    NSInvocation,NSUndoManager是该模式的典型应用;

    NSInvocation对应ConcreteCommand

    @interface NSInvocation : NSObject
    
    + (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;
    
    @property (nullable, assign) id target;
    @property SEL selector;
    ...
    - (void)invoke;
    ....
    @end
    

    其中target就是接受者receiver,并将receiver的操作action用selector保存;invoke方法即为具体命令的执行execute;

    NSInvocation.png

    简单使用

     NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
     NSInvocation *command = [NSInvocation invocationWithMethodSignature:sig];
     command.target = self;
     command.selector = @selector(test:);
     NSString *param = @"from command";
     [command setArgument:&param atIndex:2];
     [command invoke];
    

    完整代码


    参考:
    《Objective-C编程之道》
    《精通Swift设计模式》
    《大话设计模式》
    设计模式:状态模式(State)

    相关文章

      网友评论

        本文标题:独孤九剑--设计模式(iOS行为型篇)

        本文链接:https://www.haomeiwen.com/subject/pppmldtx.html