独孤九剑--设计模式(iOS创建型篇)
独孤九剑--设计模式(iOS结构型篇)
观察者模式(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
UML类图
- Subject:抽象主题角色,把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。
- ConcreteSubject:具体主题角色,在内部状态改变时,给所有注册过的观察者发出通知。
- Observer:抽象观察者角色,为所有的观察者定义一个接口,在得到主题的通知更新自己。
- ConcreteObserver:具体观察者角色,该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态和主题的状态相协调。
iOS系统中的应用
iOS中NSNotificationCenter、KVO都是观察者模式的运用
示例代码
标准格式的观察者代码,详见demo中/自定义观察者/部分;
这里仿写一下系统NotificationCenter的简单版本,省去抽象角色Subject、ConcreteSubject,实现一个观察者功能;
- 新建通知中心单例
// ConcreteSubject
class NotificationCenter {
static let `default` = NotificationCenter()
private init() {}
}
- NotificationCenter中定义观察者集合
// NotificationCenter可以添加不同name的监听,这里直接使用字典记录每个name下所有的观察者
var observers: [String : [Observation]] = [:]
- 封装观察的信息
// ConcreteObserver
class Notification: NSObject {
let name: String
var userInfo: [String : Any]?
init(name: String, userInfo: [String : Any]? = nil) {
self.name = name
self.userInfo = userInfo
}
}
- 定义观察者封装类
// 封装观察者对象、实现的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)
}
}
}
- 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:抽象中介者,定义了同事对象到中介者对象之间的接口。
- 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种关系;
如果系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象;
使用中介者模式优化
- 抽象中介者协议
// Mediator
protocol RouterMediator {
var viewControllers: [String : ViewControllerSubject] { get }
mutating func register(vc: ViewControllerSubject, path: String)
func router(from: ViewControllerSubject, to path: String)
}
- 实现具体路由中介
// 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)
}
}
}
- 抽象路由的Controller类,提供mediator引用
// Colleague
class ViewControllerSubject: UIViewController {
var router: RouterMediator?
}
- 具体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类图
- 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来决定哪个算法;
可以把相关算法分离成不同的类,成为策略。
- 策略模式
- 抽象策略协议,提供算法接口
protocol Strategy {
associatedtype T
func algorithm(a: T, b: T) -> T
}
- 创建具体策略类,实现对应算法
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
}
}
- 创建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类图
- Context:环境角色,定义操作接口,维护不同状态。
- State:抽象状态角色,定义状态接口。
- ConcreteState:具体状态角色,实现Context的一个状态所对应的行为。
示例
以线上定房为例,房间的状态有:空闲、已预订、已入住。空闲房间的状态可以转变为:已预订、已入住。已预订状态房间的状态可以转变为:已入住、空闲。已入住房间的状态可以转变为:空闲。
- 抽象房间状态,定义接口
// state
protocol RoomState {
func book() -> Bool
func checkIn() -> Bool
func checkOut() -> Bool
}
- 创建具体房间状态类,实现对应方法
// 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
}
}
- 创建房间环境类,定义实现操作接口,维护状态逻辑
// 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:抽象命令角色,声明一个给所有命令类的抽象接口。
- ConcreteCommand:具体命令角色,定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。
- Invoker:请求者角色,负责调用命令对象执行请求。
- Receiver:接收者角色,负责具体实施和执行一个请求。
示例
编写一个电视遥控功能,不同的按键处理不同的操作;
- 定义receiver电视类,支持打开、关闭、切换频道功能
// receiver
struct TV {
func open() {
print("tv open")
}
func close() {
print("tv close")
}
func `switch`() {
print("channel switch")
}
}
- 定义抽象命令及具体命令
每个具体命令对应不同的操作,内部实际是调用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命令")
}
}
- 定义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;
简单使用
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
NSInvocation *command = [NSInvocation invocationWithMethodSignature:sig];
command.target = self;
command.selector = @selector(test:);
NSString *param = @"from command";
[command setArgument:¶m atIndex:2];
[command invoke];
参考:
《Objective-C编程之道》
《精通Swift设计模式》
《大话设计模式》
设计模式:状态模式(State)