设计模式-行为型

设计模式-行为型

行为型设计模式主要用于软件运行时复杂的流程控制。包含:模板方法模式、策略模式、命令模式、职责链模式、状态模式、观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式和解释器模式

模板方法模式

在软件设计时,很多时候系统的运行流程都是确定的,在整个流程中,可能只有部分环节的具体实现是有差别的,这时我们就可以使用模板方法模式,其具体定义为:定义一个操作流程中的算法骨架,将部分算法环节的实现延迟到子类中,使子类可以在不改变算法骨架的前提下对特定步骤进行定制。

以职员的工作流程为例:

class Management {
    func clockIn() {
        print("上班")
    }
    func working() {
        print("工作")
    }
    func clockOut() {
        print("下班")
    }
    func start() {
        clockIn()
        working()
        clockOut()
    }
}

无论对于任何岗位的职员,这个流程都不变,对于不同的岗位不同的是具体的工作内容,例如添加一位工程师,以模板方法模式设计:

重构后

...
class Engineer: Management {
    override func working() {
        print("软件设计")
    }
}

使用模板方法模式设计后,代码的复用性更强,但是因为子类修改了父类的方法的实现,有悖里氏替换原则,因此在选择时需要根据具体场景进行分析。

策略模式

策略模式核心原则是定义一系列算法,将每个算法独立封装,使用者可以灵活的进行选择替换。

例如现实生活中到某地的出行方式有很多种,可以灵活选择:出租车、公交车、地铁、自行车等,需要根据路程远近和交通状况灵活的选择,这就是一种策略模式。

重构后

protocol Transport {
    func toDestination()
}
class Taxi: Transport {
    func toDestination() {
        print("出租车")
    }
}
class Bus: Transport {
    func toDestination() {
        print("公交车")
    }
}
class Subway: Transport {
    func toDestination() {
        print("地铁")
    }
}
class Action {
    var destination: String
    var transport: Transport
    init(destination: String, transport: Transport) {
        self.destination = destination
        self.transport = transport
    }
    func go() {
        self.transport.toDestination()
    }
}
let action = Action(destination: "北京", transport: Subway())
action.go()

通过策略模式,不同的Action对象调用go方法很容易根据场景实现不同的行为。

命令模式

命令模式的核心是将请求封装为对象,使得请求的发起与执行分开,发起方和执行方通过命令进行交互。
以教务系统为例,

struct Teacher {
    var name: String
    var subject: String
    func log() {
        print("\(name) + \(subject)")
    }
}
class School {
    var teachers = [Teacher]()
    func addTeacher(name: String, subject: String) {
        teachers.append(Teacher(name: name, subject: subject))
    }
    func deleteTeacher(name: String) {
        teachers = teachers.filter {$0.name != name}
    }
    func show() {
        for teacher in teachers {
            teacher.log()
        }
    }
}
let school = School()
school.addTeacher(name: "学伟", subject: "计算机")
school.addTeacher(name: "张三", subject: "体育")
school.addTeacher(name: "李四", subject: "数学")
school.show()
school.deleteTeacher(name: "李四")
school.show()

其中,School 提供了展示所有教师信息的方法,也提供了添加和删除教师的方法,通过这种方式对教师的操作难以维护,可以使用命令模式对其重构,将添加和删除教师、展示所有教师的逻辑都封装成一种命令。

重构后

struct Teacher {
    var name: String
    var subject: String
    func log() {
        print("\(name) + \(subject)")
    }
}
class SchoolCommand {
    enum ActionType {
        case add
        case delete
        case show
    }
    var type: ActionType
    var name: String?
    var subject: String?
    init(type: ActionType, name: String? = nil, subject: String? = nil) {
        self.type = type
        self.name = name
        self.subject = subject
    }
}
class School {
    var teachers = [Teacher]()
    func runCommand(command: SchoolCommand) {
        switch command.type {
        case .add:
            addTeacher(name: command.name!, subject: command.subject!)
        case .delete:
            deleteTeacher(name: command.name!)
        case .show:
            show()
        }
    }
    private func addTeacher(name: String, subject: String) {
        teachers.append(Teacher(name: name, subject: subject))
    }
    private func deleteTeacher(name: String) {
        teachers = teachers.filter {$0.name != name}
    }
    private func show() {
        for teacher in teachers {
            teacher.log()
        }
    }
}
let school = School()
school.runCommand(command: SchoolCommand(type: .add, name: "学伟", subject: "计算机"))
school.runCommand(command: SchoolCommand(type: .add, name: "张三", subject: "体育"))
school.runCommand(command: SchoolCommand(type: .add, name: "李四", subject: "数学"))
school.runCommand(command: SchoolCommand(type: .show))
school.runCommand(command: SchoolCommand(type: .delete,name: "李四"))
school.runCommand(command: SchoolCommand(type: .show))

使用命令模式重构后,对于 School 的操作都通过 命令 SchoolCommand 触发,代码扩展性更强,且命令可以作为对象直接被存储、传输、重复和撤销,在某些场景下会非常有用。

职责链模式

一个请求被发出,从低层向高层依次寻找可以处理此请求的对象,直到找到处理者才结束责任链。

重构后

struct Requet {
    enum Level {
        case low
        case middle
        case high
    }
    var level: Level
}
protocol Handler {
    var nextHandler: Handler? { get }
    func handlerRequest(request: Requet)
    func nextHanderDo(request: Requet)
}
extension Handler {
    func nextHanderDo(request: Requet) {
        if let nextHandler = nextHandler {
            nextHandler.handlerRequest(request: request)
        } else {
            print("无法处理请求")
        }
    }
}
class HighHandler: Handler {
    var nextHandler: Handler? = nil
    func handlerRequest(request: Requet) {
        if request.level == .high {
            print("HighHandler 处理请求")
        } else {
            nextHanderDo(request: request)
        }
    }
}
class MiddleHandler: Handler {
    var nextHandler: Handler? = HighHandler()
    func handlerRequest(request: Requet) {
        if request.level == .middle {
            print("MiddleHandler 处理请求")
        } else {
            nextHanderDo(request: request)
        }
    }
}
class LowHandler: Handler {
    var nextHandler: Handler? = MiddleHandler()
    func handlerRequest(request: Requet) {
        if request.level == .low {
            print("LowHandler 处理请求")
        } else {
            nextHanderDo(request: request)
        }
    }
}
class Chain: Handler {
    var nextHandler: Handler? = LowHandler()
    func handlerRequest(request: Requet) {
        nextHandler?.handlerRequest(request: request)
    }
}
var request = Requet(level: .low)
Chain().handlerRequest(request: request)
request = Requet(level: .middle)
Chain().handlerRequest(request: request)
request = Requet(level: .high)
Chain().handlerRequest(request: request)

外界只需传入指定等级的请求,责任链内部即可根据等级选择相应的处理逻辑。

责任链模式的核心是将请求发送到责任链上,链上的每一个处理者可以根据实际情况决定是否处理此请求,如果不能处理则将请求继续向上发送,直到被某个处理者处理或者没有处理者为止。这种结构可以灵活地向责任链中增加或删除处理者,对于不同种类的请求,发出方只需要将其发送到责任链上,不需要关心具体被哪一个处理者处理。降低了对象间的耦合性,并且使责任的分担更加清晰。

状态模式

状态模式的核心是:当控制一个对象行为的状态转换过于复杂时,把状态处理的逻辑分离出到单独的状态类中。
在软件设计中,对象在不同的情况下会表现出不同的行为,被称为有状态的对象。影响对象行为的属性被称为状态,影响对象行为的属性被称为状态。对有状态的对象进行编程时,使用状态设计模式可以使代码的内聚性更强。

重构后

class StateContent {
    var currentState: State
    init(_ currentState: State) {
        self.currentState = currentState
    }
    func changeState(curState: State) {
        self.currentState = curState
    }
}
protocol State {
    func info()
    func doAction(content: StateContent)
}
class Open: State {
    func info() {
        print("开灯")
    }
    func doAction(content: StateContent) {
        content.currentState = Open()
    }
}
class Close: State {
    func info() {
        print("关灯")
    }
    func doAction(content: StateContent) {
        content.currentState = Close()
    }
}
class LightButton {
    var stateContent: StateContent
    init(state: State) {
        self.stateContent = StateContent(state)
    }
    func change(state: State) {
        self.stateContent.changeState(curState: state)
    }
    func log() {
        stateContent.currentState.info()
    }
}
let light = LightButton(state: Close())
light.log()
light.change(state: Open())
light.log()

其中 StateContent 定义了状态的上下文,用来维护当前开关的状态。而 OpenClose 则是对状态的封装。

观察者模式

观察者模式又被称为发布-订阅模式,在观察者模式中,一个对象发生变化会通知到所有依赖它的对象,依赖它的对象可以根据情况进行自身行为的更改。
在iOS开发中,通知中心和键值监听系统的实现都使用了观察者模式。如下代码通过实现一个简易的通知中心演示观察者模式

重构后

typealias XWNotificationCallback = (XWNotification) -> Void
struct XWNotification {
    var name: String
    var data: String
    var object: AnyObject?
    func info() {
        print("name: \(name), data: \(data), object: \(String(describing: object))")
    }
}
struct XWObsever {
    var object: AnyObject
    var callback: XWNotificationCallback
}
class XWNotificationCenter {
    static let shared = XWNotificationCenter()
    private var observers = Dictionary<String, Array<XWObsever>>()
    private init() {}
    func addObserver(name: String, object: AnyObject, callback: @escaping XWNotificationCallback) {
        let observer = XWObsever(object: object, callback: callback)
        if var curObserver = observers[name] {
            curObserver.append(observer)
        } else {
            observers[name] = [observer]
        }
    }
    func removeObserver(name: String) {
        observers.removeValue(forKey: name)
    }
    func postNotification(notification: XWNotification) {
        if let array = observers[notification.name] {
            var postNotification = notification
            for observer in array {
                postNotification.object = observer.object
                observer.callback(postNotification)
            }
        }
    }
}
let key = "KEY"
XWNotificationCenter.shared.addObserver(name: key, object: "监听者A" as AnyObject) { noti in
    noti.info()
}
//XWNotificationCenter.shared.removeObserver(name: key)
XWNotificationCenter.shared.postNotification(notification: XWNotification(name: key, data: "通知内容"))

以上就是一个简易通知中心的实现,当添加了监听之后,一旦通知被发出,回调方法就会立刻执行,对于相同名称的通知,可以添加多个观察者。

中介者模式

中介者模式的核心是将网状的对象交互结构改为星形结构,即所有的对象都与一个中介者进行交互。使用中介者模式可以使原本耦合性很强的对象间的耦合变得松散,提高系统的灵活性和扩展性。

如下代码演示了网状的对象交互结构

class ServerA {
    func handleClientA() {
        print("ServerA 处理 ClientA 的请求")
    }
    func handleClientB() {
        print("ServerA 处理 ClientB 的请求")
    }
}
class ServerB {
    func handleClientA() {
        print("ServerB 处理 ClientA 的请求")
    }
    func handleClientB() {
        print("ServerB 处理 ClientB 的请求")
    }
}
class ClientA {
    func requestServerA() {
        ServerA().handleClientA()
    }
    func requestServerB() {
        ServerB().handleClientA()
    }
}
class ClientB {
    func requestServerA() {
        ServerA().handleClientB()
    }
    func requestServerB() {
        ServerB().handleClientB()
    }
}
let clientA = ClientA()
clientA.requestServerA()
clientA.requestServerB()
let clientB = ClientB()
clientB.requestServerA()
clientB.requestServerB()

如上所述,两个客户端可以分别与服务端进行交互,有时客户端也可以点对点的与另外的客户端进行交互,这样会使系统的结构更加复杂,可以通过中介者模式统一客户端与服务端的交互逻辑

重构后

class ServerA {
    func handleClientA() {
        print("ServerA 处理 ClientA 的请求")
    }
    func handleClientB() {
        print("ServerA 处理 ClientB 的请求")
    }
}
class ServerB {
    func handleClientA() {
        print("ServerB 处理 ClientA 的请求")
    }
    func handleClientB() {
        print("ServerB 处理 ClientB 的请求")
    }
}
class ClientA {}
class ClientB {}
class Mediator {
    static func handler(client: AnyObject, server: AnyObject) {
        if client is ClientA {
            if server is ServerA {
                ServerA().handleClientA()
            } else {
                ServerB().handleClientA()
            }
        } else {
            if server is ServerA {
                ServerA().handleClientB()
            } else {
                ServerB().handleClientB()
            }
        }
    }
}
let clientA = ClientA()
let clientB = ClientB()
let serverA = ServerA()
let serverB = ServerB()
Mediator.handler(client: clientA, server: serverA)
Mediator.handler(client: clientA, server: serverB)
Mediator.handler(client: clientB, server: serverA)
Mediator.handler(client: clientB, server: serverB)

重构后客户端相关类中无须知道服务端具体的实现细节,中介者统一封装了这些逻辑。

迭代器模式

软件设计中,很多对象都是以聚合的方式组成的,或者其内部包含集合类型的数据,在访问对象时,通常需要通过遍历的方式获取到其中的各个元素。这样,如果对象内部组合的方式产生了变化就必须对源码进行修改。

迭代器模式的核心是提供一个对象来访问聚合对象中的一系列数据,不暴露聚合对象内部的具体实现,这样即保证了类的安全性,也将内部的集合遍历逻辑与聚合对象本身进行了分离。

重构后

protocol Iterator {
    associatedtype ObjectType
    var cursor: Int { get }
    func next() -> ObjectType?
    func reset()
}
class School: Iterator {
    private var teachers = [String]()
    typealias ObjectType = String
    var cursor: Int = 0
    func next() -> String? {
        if cursor < teachers.count {
            let teacher = teachers[cursor]
            cursor += 1
            return teacher
        } else {
            return nil
        }
    }
    
    func reset() {
        cursor = 0
    }
    
    func addTeacher(name: String) {
        teachers.append(name)
    }
}
let school = School()
school.addTeacher(name: "学伟")
school.addTeacher(name: "小王")
school.addTeacher(name: "乔布斯")
while let teacher = school.next() {
    print(teacher)
}
print("遍历完成")

外界对 School 内部的数组是不感知的,使用迭代器模式可以很好的对内部实现进行封闭,外部除了通过类中暴露的函数来操作 teachers 数组外,不能直接操作。
Swift标准库中,可以直接使用官方迭代器协议 IteratorProtocol

访问者模式

当数据的类型固定,但对其访问的操作相对灵活时,可以采用访问者模式对软件系统进行设计。访问者模式的核心是将数据的处理方式从数据结构中分离出来,之后可以方便地对数据的处理方法进行扩展。
举一个现实生活中应用访问者模式的例子:作为一种数据,不同的角色对其访问会有不同的行为表现,对于景区门票这一数据,作为游客需要购买,作为验票员需要验票,这种场景:

重构后

struct Ticket {
    var name: String
}
protocol Visitor {
    func visit(ticket: Ticket)
}
class Tourist: Visitor {
    func visit(ticket: Ticket) {
        print("游客购买\(ticket.name)")
    }
}
class Guard: Visitor {
    func visit(ticket: Ticket) {
        print("检票员检查了\(ticket.name)")
    }
}
let ticket = Ticket(name: "公园门票")
let tourist = Tourist()
tourist.visit(ticket: ticket)
let guarder = Guard()
guarder.visit(ticket: ticket)

如上,不同角色对门票的操作分别封装在了独立的类中,这使之后新增行为变得非常容易,例如财务人员对门票价格进行核对等。

备忘录模式

备忘录模式的定义:在不破坏封装性的前提下,对一个对象的状态进行保存,在需要时,可以方便地恢复到原来保存的状态,备忘录模式又被称为快照模式。
从功能上讲,备忘录模式与命令模式有许多相似之处,都是提供了一种恢复状态的机制;不同的是,命令模式是将操作封装成命令,命令可以回滚,备忘录模式则是存储对象某一时刻的状态,可以将状态进行重置。
例如,很多应用都提供了用户自定义偏好设置的功能,偏好设置的保存与重置可以采用备忘录模式实现。

重构后

protocol MementoProtocol {
    func allKeys() -> [String]
    func valueForKey(key: String) -> Any
    func setValue(value: Any, key: String)
}
class Setting: MementoProtocol {
    var setting1 = false
    var setting2 = false
    func allKeys() -> [String] {
        return ["setting1", "setting2"]
    }
    
    func valueForKey(key: String) -> Any {
        switch key {
        case "setting1":
            return setting1
        case "setting2":
            return setting2
        default:
            return ""
        }
    }
    
    func setValue(value: Any, key: String) {
        switch key {
        case "setting1":
            setting1 = value as? Bool ?? false
        case "setting2":
            setting2 = value as? Bool ?? false
        default:
            print("key: \(key) 设置错误")
        }
    }
    func show() {
        print("setting1: \(setting1) ++ setting2: \(setting2)")
    }
}
class MementoManager {
    var dictionary = [String: [String: Any]]()
    func saveState(obj: MementoProtocol, stateName: String) {
        var dict = [String: Any]()
        for key in obj.allKeys() {
            dict[key] = obj.valueForKey(key: key)
        }
        dictionary[stateName] = dict
    }
    func resetState(obj: MementoProtocol, stateName: String) {
        if let dict = dictionary[stateName] {
            for kv in dict {
                obj.setValue(value: kv.value, key: kv.key)
            }
        }
    }
}
var setting = Setting()
let manager = MementoManager()
setting.setting1 = true
setting.setting2 = true
manager.saveState(obj: setting, stateName: "vip")
setting.setting2 = false
manager.saveState(obj: setting, stateName: "super")
setting.show()
manager.resetState(obj: setting, stateName: "vip")
setting.show()
manager.resetState(obj: setting, stateName: "super")
setting.show()

MementoManager 是一个快照管理类,可以将任何符合 MementoProtocol 协议的对象进行快照保存。一个对象可以保存多个快照,在需要时可以方便地恢复到某个快照。有存档机制的软件可以按照备忘录设计模式的思路实现。

解释器模式

定义一种简洁的语言,通过实现一个解释器来对语言进行解析,从而实现逻辑。
正则表达式和iOS开发中用于自动布局的 VFL(Visual Format Language)是对解释器模式的应用。

例如,在软件中的页面路由跳转可以采用解释器模式进行设计。

重构后

class Interpreter {
    static func handler(string: String) {
        let proto = string.components(separatedBy: "://")
        if let pro = proto.first {
            print("路由协议: \(pro)")
            if proto.count > 1, let last = proto.last {
                let path = last.split(separator: "?", maxSplits: 2, omittingEmptySubsequences: true)
                if let pathFirst = path.first {
                    print("路由路径: \(pathFirst)")
                    if path.count > 1, let param = path.last {
                        print("路由参数: \(param)")
                    }
                }
            }
        }
    }
}
Interpreter.handler(string: "http://www.xxx.com?key=value")

此类用于解析某逻辑的设计模式即解释器模式的应用。

总结

  • 模板方法模式:定义算法骨架的前提下允许对关键环节的算法实现做修改
  • 策略模式:定义一系列方便切换的算法实现
  • 命令模式:将操作封装为命令对象
  • 责任链模式:通过责任链对请求进行处理,隐藏处理请求的对象细节
  • 状态模式:将变化的属性封装为状态对象进行统一管理
  • 观察者模式:通过监听的方式处理对象间的交互逻辑
  • 中介者模式:通过定义中介者来将网状结构的逻辑改为星状结构
  • 迭代器模式:提供一种访问对象内部集合数据的接口
  • 访问者模式:将数据的操作与数据本身分离
  • 备忘录模式:通过快照的方式存储对象的状态
  • 解释器模式:通过编写解释器对自定义的简单语言进行解析,从而实现逻辑