本节主要涉及行为型模式:观察者模式、迭代器模式、状态模式、备忘录模式
行为型模式
观察者模式 Observer
概念
定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
示例 UML 类图
示例代码实现
Subject 主题或称发布者接口,是被观察者,可以是类或者接口,这里使用接口。其定义了attach()
和detach()
方法来用于添加、移除观察者对象,同时还有notifyAllObservers()
用于通知观察者们;
ConcreteSubject 具体的主题,其实现了 Subject ,其内部包含一个observers
成员变量用于存储观察者们以及一些变化的数据例如state
,当数据状态变化时,调用notifyAllObservers()
通知所有观察者;其内部还可以定义一些业务逻辑用于更改状态、发起通知,或者完全供外部调用通知方法并传递参数通知所有观察者;
Observer,观察者或称订阅者接口,声明了接到更新时的方法update()
ConcreteObserver 具体的观察者,实现了 Observer 中的update()
方法,当接到更新通知是执行一定操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| interface Subject { attach(state: Observer): void detach(observer: Observer): void notifyAllObservers(): void }
class ConcreteSubject implements Subject { private observers: Observer[] = [] public state: number = 0 public attach(observer: Observer) { if (this.observers.includes(observer)) { console.log('Observer 观察者已存在') return } this.observers.push(observer) } public detach(observer: Observer) { const index = this.observers.indexOf(observer) if (index === -1) { console.log('Observer 观察者不存在') return } this.observers.splice(index, 1) } public notifyAllObservers() { this.observers.forEach((observer) => { observer.update(this) }) } public doSomething() { console.log('Subject: 进行一些业务,将 state 改为2,并通知 Observers 更新') this.state = 2 this.notifyAllObservers() } }
interface Observer { update(subject: Subject): void }
class ConcreteObserver implements Observer { public name: string constructor(name: string) { this.name = name } public update(subject: Subject) { const concreteSubject = subject as ConcreteSubject console.log(`${this.name} 接到更新通知了,最新的 state 是 ${concreteSubject.state}`) } }
const subject = new ConcreteSubject()
const observerA = new ConcreteObserver('observerA') subject.attach(observerA)
const observerB = new ConcreteObserver('observerB') subject.attach(observerB)
subject.doSomething()
|
适用场景
观察者模式使用频率也相当高,诸如 DOM 中事件绑定相关背后都是观察者模式的体现
- 当一个对象的改变需要同时改变其他对象时
- 当一些对象必须观察其他对象的状态变化来处理自身业务时
分析
- 符合开放封闭原则,主题和观察者分离,无需修改原有主题代码就可以增加观察者
- 通过定义一个消息通知机制,可以实现展现层和数据层的分离,可以有各式各样的展现层作为具体观察者
- 注意防止观察者和主题之间循环调用,防止出现崩溃
迭代器模式 Iterator
概念
提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示。
示例 UML 类图
示例代码实现
Iterator 是抽象迭代器接口,定义了访问元素下一个和判断是否有下一个的方法,这是迭代器运行的核心
ConcreteIterator 是具体的迭代器,实现了 Iterator,其中 index 作为游标来记录当前位置
Aggregate 是抽象聚合类接口,可以理解为一种 Collection 集合,声明了聚合内部需要返回一个 具体的迭代器实例,类似一个迭代器工厂
ConcreteAggregate 是具体的聚合类,实现了 Aggregate 内部存储管理聚合数据,并返回一个 ConcreteIterator 具体迭代器
实际前端应用中,可能都会简化这个结构,例如不需要 Aggregate 和 ConcreteAggregate,直接创建 Iterator 来进行数据遍历;将遍历顺序改为倒序等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| interface Iterator<T> { hasNext(): boolean next(): T | null }
class ConcreteIterator<T> implements Iterator<T> { protected list: Array<T> protected index: number constructor(list: Array<T>) { this.list = list this.index = 0 }
public hasNext() { if (this.index > this.list.length - 1) { return false } return true } public next() { if (this.hasNext()) { return this.list[this.index++] } return null } }
interface Aggregate<T> { createIterator(): Iterator<T> }
class ConcreteAggregate<T> implements Aggregate<T> { public list: Array<T> constructor(list: Array<T>) { this.list = list } createIterator() { return new ConcreteIterator(this.list) } }
const collection = new ConcreteAggregate(['h','e','l','l','o']) const iterator = collection.createIterator()
while (iterator.hasNext()) { console.log(iterator.next()) }
|
在这里不得不提下 ES6 中的 Iterator,其主要目的是:
- 为诸多的有序集合数据类型提供一个统一的遍历接口
- 为 for…of 语法实现循环遍历
ES6 的 Iterator 存储在所符合数据结构的 Symbol.iterator
属性上,其存储的是个函数,可以用来判断是否为有序集合可以 for…of 遍历。
比如 执行 String.prototype.[Symbol.iterator]()
返回一个迭代器
1 2 3 4 5
| String.prototype[Symbol.iterator] ƒ [Symbol.iterator]() { [native code] }
String.prototype[Symbol.iterator]() StringIterator {}
|
其返回迭代器上也实现了next()
方法,其中包含 value
和 done
,用来返回值和判断是否遍历结束
1 2
| String.prototype[Symbol.iterator]().next() {value: undefined, done: true}
|
具备 Iterator 的数据结构有:
Array, String, Map, Set, TypedArray, 函数的 arguments 入参对象, NodeList
Generator 中也实现了 Iterator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function* testGenerator() { yield 'test' yield 'this' return 'end' } const newTest = testGenerator() newTest[Symbol.iterator]
newTest.next() {value: "test", done: false} newTest.next() {value: "this", done: false} newTest.next() {value: "end", done: true}
for (let item of testGenerator()) { console.log(item) } VM4093:2 test VM4093:2 this
|
适用场景
- 想要为不同的或者未知的数据结构创造一个统一的遍历接口,同时不修改暴露内部数据结构
- 想要对聚合对象增加多种迭代遍历方法时
分析
- 符合单一职责原则,各个迭代器都完成自己的独立职责,诸多的遍历代码可以被封装为独立的类
- 符合开放封闭原则,无需修改原有数据聚合对象,可增量创建不同的迭代器
- 迭代器数量随着目标数据聚合类的增加,一定程度上增加了系统复杂性
状态模式 State
概念
允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了自身所属的类。
示例 UML 类图
示例代码实现
Context 是上下文环境类,保存了一个对具体状态 State 实例的引用,并将和状态相关的行为委托给这个状态实例,当状态改变时其对应的行为方法也在发生对应的改变;
State 是抽象状态类,抽象了状态应该实现的不同方法,并保存了对 Context 的引用供子类使用,同时一些公用的状态方法可以写在这里;
NormalState… 这些是具体状态类,他们继承了 State,不同状态各自实现自己的状态方法,而且还能根据 Context 的引用来触发状态改变;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| class Context { private state: State constructor() { this.state = new NoDiskState(this) } public changeState(state: State) { console.log(`State 变更为: ${state.constructor.name}`) this.state = state }
public insertDisc() { console.log('insertDisc 插入光盘') this.changeState(new NormalState(this)) } public removeDisc() { this.state.removeDisc() } public play() { this.state.play() } public playNext() { this.state.playNext() } }
abstract class State { protected context: Context constructor(context: Context) { this.context = context } public abstract play(): void public abstract playNext(): void public abstract removeDisc(): void }
class NormalState extends State { public play() { console.log('NormalState: 开始播放') } public playNext() { console.log('NormalState: 开始播放 下一个节目') } public removeDisc() { console.log('NormalState: 弹出光盘') this.context.changeState(new NoDiskState(this.context)) } }
class NoDiskState extends State { public play() { console.log('NoDiskState: 请插入光盘') } public playNext() { console.log('NoDiskState: 无法播放下一首,请插入光盘') } public removeDisc() { console.log('NoDiskState: 已无光盘,请插入光盘') } } const bluRayPlayer = new Context() bluRayPlayer.play() bluRayPlayer.insertDisc() bluRayPlayer.play() bluRayPlayer.playNext() bluRayPlayer.removeDisc() bluRayPlayer.play()
|
适用场景
状态模式的应用场景在日常开发中也很常见,大部分地方都有状态的概念,比如:网站文章状态、文件的上传状态等。
- 对象需要根据自身状态进行不同的行为,而且状态类型也相当多,具体状态的代码实现也可能频繁变更
- 大量的条件语句处理状态时,状态有较多的重复代码,状态模式可以将其抽离到抽象类里
分析
- 符合单一职责原则,每个状态具体的行为代码相互独立
- 符合开放封闭原则,增量增加状态无需修改原有状态和上下文
- 注意状态切换相关结构设计,因可以在具体状态中进行状态切换,可能会导致整体结构混乱、链路不清晰
备忘录模式 Memento
概念
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
示例 UML 类图
示例代码实现
Originator 为原发器,可以创建一个需要记录自身状态,生成备忘快照的实例,多把编辑器内容区、可视化工具组件等需要保持状态的类设计成原发器;
Memento 为备忘录,用来存储 Originator 中对应的状态等,其根据原发器需要存储的结构进行设计,额外存储一些时间戳等;
Caretaker 为负责人,可以理解为历史记录管理器,将 Memento 有序化存储,并按需取出、展示 Memento
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
| class Originator { private state: string constructor() { this.state = '' } public saveMemento() { return new Memento(this.state) } public restoreMemento(memento: Memento) { this.state = memento.getState() console.log(`Originator: 恢复数据为 ${this.state}`) } public writeSomething(str: string) { this.state += str console.log(`Originator: 修改数据为 ${this.state}`) } }
class Memento { private state: string private date: string constructor(state: string) { this.state = state this.date = new Date().toLocaleString() } public getState() { return this.state } public getDate() { return this.date } }
class Caretaker { private originator: Originator private mementos: Memento[] constructor(originator: Originator) { this.originator = originator this.mementos = [] } public save() { this.mementos.push(this.originator.saveMemento()) } public undo() { if (this.mementos.length === 0) { return } const memento = this.mementos.pop() as Memento this.originator.restoreMemento(memento) } public showHistory() { console.log(`Caretaker: 修改历史:`); this.mementos.forEach((memento) => { console.log(`${memento.getDate()} - ${memento.getState()}`) }) } }
const editor = new Originator() const historyAdmin = new Caretaker(editor)
historyAdmin.save() editor.writeSomething('123')
historyAdmin.save() editor.writeSomething('hello')
historyAdmin.showHistory() historyAdmin.undo()
class Originator { private state: string constructor() { this.state = '' } public saveMemento() { return new Memento(this.state) } public restoreMemento(memento: Memento) { this.state = memento.getState() console.log(`Originator: 恢复数据为 ${this.state}`) } public writeSomething(str: string) { this.state += str console.log(`Originator: 修改数据为 ${this.state}`) } }
class Memento { private state: string private date: string constructor(state: string) { this.state = state this.date = new Date().toLocaleString() } public getState() { return this.state } public getDate() { return this.date } }
class Caretaker { private originator: Originator private mementos: Memento[] constructor(originator: Originator) { this.originator = originator this.mementos = [] } public save() { this.mementos.push(this.originator.saveMemento()) } public undo() { if (this.mementos.length === 0) { return } const memento = this.mementos.pop() as Memento this.originator.restoreMemento(memento) } public showHistory() { console.log(`Caretaker: 修改历史:`); this.mementos.forEach((memento) => { console.log(`${memento.getDate()} - ${memento.getState()}`) }) } }
const editor = new Originator() const historyAdmin = new Caretaker(editor)
historyAdmin.save() editor.writeSomething('123')
historyAdmin.save() editor.writeSomething('hello')
historyAdmin.showHistory() historyAdmin.undo()
|
适用场景
各类涉及到编辑内容的程序都涉及到撤销操作,延伸到游戏存档等都可以涉及到备忘录模式思想的应用
- 目标对象需要记录状态快照,按需恢复至之前状态时可以恢复至之前的状态
分析
- 提供了一种恢复机制,新状态无效或者出问题时恢复至之前的状态
- 可以在不破坏对象封装的前提下创建对象的快照
- 注意控制备忘录存储大小,比如设置可撤销步骤上限,防止过渡消耗资源
- Javascript 中考虑 Immutable 不可变数据,防止快照中状态被改变
参考
相关文章