设计模式-行为型
行为型设计模式主要用于软件运行时复杂的流程控制。包含:模板方法模式、策略模式、命令模式、职责链模式、状态模式、观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式和解释器模式
模板方法模式
在软件设计时,很多时候系统的运行流程都是确定的,在整个流程中,可能只有部分环节的具体实现是有差别的,这时我们就可以使用模板方法模式,其具体定义为:定义一个操作流程中的算法骨架,将部分算法环节的实现延迟到子类中,使子类可以在不改变算法骨架的前提下对特定步骤进行定制。
以职员的工作流程为例:
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
定义了状态的上下文,用来维护当前开关的状态。而 Open
、Close
则是对状态的封装。
观察者模式
观察者模式又被称为发布-订阅模式,在观察者模式中,一个对象发生变化会通知到所有依赖它的对象,依赖它的对象可以根据情况进行自身行为的更改。
在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")
此类用于解析某逻辑的设计模式即解释器模式的应用。
总结
- 模板方法模式:定义算法骨架的前提下允许对关键环节的算法实现做修改
- 策略模式:定义一系列方便切换的算法实现
- 命令模式:将操作封装为命令对象
- 责任链模式:通过责任链对请求进行处理,隐藏处理请求的对象细节
- 状态模式:将变化的属性封装为状态对象进行统一管理
- 观察者模式:通过监听的方式处理对象间的交互逻辑
- 中介者模式:通过定义中介者来将网状结构的逻辑改为星状结构
- 迭代器模式:提供一种访问对象内部集合数据的接口
- 访问者模式:将数据的操作与数据本身分离
- 备忘录模式:通过快照的方式存储对象的状态
- 解释器模式:通过编写解释器对自定义的简单语言进行解析,从而实现逻辑