共计 3348 个字符,预计需要花费 9 分钟才能阅读完成。
Memento(备忘录模式)
Memento(备忘录模式)属于行为型模式,是针对如何捕捉与复原对象外部状态的设计模式。
用意:在不毁坏封装性的前提下,捕捉一个对象的外部状态,并在该对象之外保留这个状态。这样当前就可将该对象复原到原先保留的状态。
其实备忘录模式思维非常简单,其外围是定义了一个 Memoto(备忘录)封装对象,由这个对象解决原始对象的状态捕捉与还原,其余中央不须要感知其外部数据结构和实现原理,而且 Memoto 对象自身构造也非常简单,只有 getState
与 setState
一存一取两个办法,前面会具体解说。
举例子
如果看不懂下面的用意介绍,没有关系,设计模式须要在日常工作里用起来,联合例子能够加深你的了解,上面我筹备了三个例子,让你领会什么场景下会用到这种设计模式。
撤销重做
如果撤销重做波及到大量简单对象,每个对象外部状态的存储构造都不同,如果一个一个解决,很容易写出 case by case 的冗余代码,而且在拓展一种新对象构造时(如嵌入 ppt),还须要在撤销重做时对相应构造做解决。备忘录思维相当于一种对立封装思维,不论这个对象构造如何,都能够保留在一个 Memoto 对象中,通过 setState
设置对象状态与 getState
获取对象状态,这样对于任何类型的对象,画布都能够通过对立的 API 操作进行存取了。
游戏保留
玩过游戏的同学都晓得,许多游戏反对设置与读取多种存档,如果转换为代码模式,咱们可能心愿有这样一种 API 进行多存档治理:
// 创立一盘游戏。const game = new Game()
// 玩一会。game.play()
// 设置一个存档 (archive) 1。const gameArchive1 = game.createArchive()
// 再玩一会。game.play()
// 设置一个存档 (archive) 2。const gameArchive2 = game.createArchive()
// 再玩一会。game.play()
// 这个时候角色挂了,提醒“请读取存档”,玩家此时抉择了存档 1。game.loadArchive(gameArchive1)
// 此时游戏复原存档 1 状态,又能够欢快的游玩了。
其实在游戏保留的例子中,存档就是备忘录(Memoto),而主过程治理游戏状态时,只是简略调用了 createArchive
创立存档,与 load
读取存档,即可实现简单的游戏保留与读取性能,全程是不须要关怀游戏外部状态到底有多少,以及这么多状态须要如何一一复原的,这就是得益于备忘录模式的设计。
文章草稿保留
富文本编辑器的文档草稿保留也是一样的原理,简略一点只须要一个 Memoto 对象即可,如果要实现简单一点的多版本状态治理,只须要相似游戏保留机制,存储多个 Memoto 存档即可。
用意解释
看到这里,会发现备忘录模式与前端状态治理的保留与复原很像。以 Redux 类比:
setState
就像 reducer
解决的最终 state
状态一样,对 redux 全局状态来说,它不必关怀业务逻辑(有多少 reducer
,以及每个 reducer
做了什么),它只须要晓得任何 reducer
最初解决完后都是一个 state
对象,将其生成进去并存下来即可。
复原也是一样,initState
就相似 getState
,只有将上一次生成的 state
灌进来,就能够齐全还原某个时刻的状态,而不须要关怀这个状态外部是怎么的。
所以其实备忘录模式早已失去宽泛的利用,认真去了解后,会发现没必要去扣的太细,以及原始设计模式是如何定义的,因为通过几十年的演变,这些设计模式思路早已融入了编程框架的方方面面。
但按照常规,咱们还是再咬文嚼字解释一下用意:
用意:在不毁坏封装性的前提下,捕捉一个对象的外部状态,并在该对象之外保留这个状态。这样当前就可将该对象复原到原先保留的状态。
重点在于“不毁坏封装性”这几个字上,程序的可维护性永远是设计模式关注的重点,无论是游戏存档的例子,还是 Redux 的例子,下层框架应用状态时,都不须要晓得具体对象状态的细节,而实现这一点的就是 Memoto 这个形象的备忘录类。
结构图
Originator
:创立、读取备忘录的发起者。Memento
:备忘录,专门存储原始对象状态,并且避免 Originator 之外的对象读取。Caretaker
:备忘录管理者,个别用数组或链表治理一堆备忘录,在撤销重做或者版本治理时会用到。
代码例子
上面例子应用 typescript 编写。
上面是备忘录模式三剑客的定义:
// 备忘录
class Memento {
public state: any
constructor(state: any) {this.state = state}
public getState() {return this.state}
}
// 备忘录管理者
class Caretaker {private stack: Memento[] = []
public getMemento(){return this.stack.pop()
}
public addMemento(memoto: Memento){this.stack.push(memoto)
}
}
// 发起者
class Originator {
private state: any
public getState() {return this.state}
public setState(state: any) {this.state = state}
public createMemoto() {return new Memoto(this.state)
}
public setMemoto(memoto: Memoto) {this.state = memoto.getState()
}
public void setMemento(Memento memento) {state = memento.getState();
}
}
上面是一个简化版客户端应用的例子:
// 实例化发起者,比方画布、文章管理器、游戏管理器
const originator = new Originator()
// 实例化备忘录管理者
const caretaker = new Caretaker()
// 设置状态,别离对应:// 画布的组件操作。// 文章的输出。// 游戏的 .play()
originator.setState('hello world')
// 备忘录管理者记录一次状态,别离对应:// 画布的保留。// 文章的保留。// 游戏的保留。caretaker.setMemento(originator.createMento())
// 从备忘录管理者还原状态,别离对应:// 画布的还原。// 文章的读取。// 游戏读取存档。originator.setMemento(caretaker.getMemento())
在下面例子中,备忘录管理者存储状态是数组,所以能够实现撤销重做,如果要实现任意读档,能够将备忘录变为 Map
构造,依照 key
来读取,如果没有这些要求,存一个繁多的 Memoto
也够用了。
弊病
备忘录模式存储的是残缺状态而非 Diff,所以可能会在运行时耗费大量内存(当然在 Immutable 模式下,通过援用共享能够极大水平缓解这个问题)。
另外就是,备忘录模式曾经很大水平上被交融到古代框架中,你在应用状态管理工具时就曾经应用了备忘录模式了,所以很多状况下,不须要机械的依照下面的代码例子应用。设计模式重点在于利用它优化了程序的可维护性,而不必强求应用形式和官网形容截然不同。
总结
备忘录模式通过备忘录对象,将对象外部状态封装了起来,简化了程序复杂度,这合乎设计模式一贯遵循的“高内聚、低耦合”准则。
其实践行备忘录模式最好的例子就是 Redux,当我的项目所有状态都应用 Redux 治理时,你会发现无论是撤销重做,还是保留读取,都能够十分轻松实现,这时候,不要质疑为什么备忘录模式还在解决这种“遇不到的问题”,因为 Redux 自身就蕴含了备忘录设计模式的理念。
探讨地址是:精读《设计模式 – Memento 备忘录模式》· Issue #301 · dt-fe/weekly
如果你想参加探讨,请 点击这里,每周都有新的主题,周末或周一公布。前端精读 – 帮你筛选靠谱的内容。
关注 前端精读微信公众号
版权申明:自在转载 - 非商用 - 非衍生 - 放弃署名(创意共享 3.0 许可证)