TypeScript设计模式之备忘录、命令

看看用TypeScript怎样实现常见的设计模式,顺便复习一下。
学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到用哪个模式可以解决,UML忘了可以查,思想记住就好。
这里尽量用原创的,实际中能碰到的例子来说明模式的特点和用处。

备忘录模式 Memento 特点:通过保存对象之前的状态来使对象可以恢复到之前的样子。 用处:当对象需要保存/加载某一时刻的状态时可以考虑备忘录模式,如游戏的save/load。 注意:状态过大产生的开销。

备忘录应该经常可以看到,游戏的save/load,photoshop的历史记录,windows的还原点都是这个模式的应用。
使用时也要注意保存的状态过大时产生的开销,保存在硬盘上的还好,如果是运行时保存在内存上的,比如一些复杂对象的undo/redo操作,保存每一个状态都是很大的内存开销,这时就需要做些限制,比方设置一个历史记录栈的最大值来限定内存的使用。

备忘录的例子和下面的命令模式一起写,实现一个支持undo/redo的操作。

命令模式 Command 特点:把请求封装成命令对象,命令对象里包含有接收者,这样client只需要发送命令,接收者就可以做出相关响应或相反的响应。 用处:当需要发送者和接收者解耦时可以考虑命令模式,常用于事件响应,请求排除,undo/redo等。 注意:命令数量爆炸,需要集中维护。

下面用TypeScript简单实现一个命令模式和备忘录模式的undo/redo:
遥控器算是典型的命令模式,按个按钮就可以命令电视做相关响应,假设遥控器有三种功能,开、关和换台。

建个Command、undo/redo、备忘录以及控制接口:

interface Executable{ execute(param: any); } interface UndoRedoable{ undo(currParam: any, lastParam: any); redo(param: any); } class MemoItem{ command: Command; param: any; } interface Memento{ currPos: number; set(item: MemoItem); get(): MemoItem; getNext(): MemoItem; //找到下一个做redo findLastWithSameType(memoItem: MemoItem): MemoItem; // 找出上个同类型的command,得到参数,以这个参数来做undo操作,回到之前的状态 } interface Controllable{ channelNum: number; open(); close(); switchTo(channelNum: number); //换台 }

实现备忘录

class History implements Memento{ private memoList: Array<MemoItem> = []; // 记住所有command static defaultMemoItem: MemoItem = { command: undefined, param: {channelNum: 0} }; // undo到第一步时前面没有command了,返回一个默认command currPos: number = 0; // 当前undo/redo到了哪一个 get currIndex(): number{ // currPos是从后往前的顺序, currIndex是正向顺序 return this.memoList.length - this.currPos - 1; } set(item: MemoItem){ if(this.currPos != 0){ // 不是0的话表示已经undo过,往上叠加push前先删除后面的 this.memoList.splice(this.currIndex + 1); this.currPos = 0; // 重置currPos } this.memoList.push(item); } get(): MemoItem{ if(this.currIndex < this.memoList.length){ return this.memoList[this.currIndex]; } return History.defaultMemoItem; } getNext(): MemoItem{ if(this.currIndex + 1 < this.memoList.length){ return this.memoList[this.currIndex+1]; } return History.defaultMemoItem; } findLastWithSameType(memoItem: MemoItem): MemoItem{// 找出上个同类型的command,得到参数,以这个参数来做undo操作,回到之前的状态 for(let i = this.currIndex - 1; i >= 0; i--){ if(memoItem.constructor.name === this.memoList[i].constructor.name){ return this.memoList[i]; } } return History.defaultMemoItem; } }

undo/redo可以由个专门的管理器来管理,建个undo/redo管理器:
管理器要做的事有

使用备忘录按顺序记住所有command

undo/redo操作,并记住undo/redo到了哪一步

当undo/redo到了某一步时,再次有新的command,则在移除这步之后的command后再加新的command

class UndoRedoManager{ static readonly instance: UndoRedoManager = new UndoRedoManager(); private history: Memento = new History(); push(command: Command, param: any){ // command执行时应该push进来 this.history.set({command, param}); } redo(){ if(this.history.currPos == 0){ // 表示没undo过,当然redo也没必要了 return; } let memoItem = this.history.getNext(); // 取出上次undo过的下一个command并执行 this.history.currPos--; memoItem.command.redo(memoItem.param); } undo() { let memoItem = this.history.get(); if(memoItem === History.defaultMemoItem){ return; } let lastMemoItem = this.history.findLastWithSameType(memoItem); this.history.currPos++; memoItem.command.undo(memoItem.param, lastMemoItem.param); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/16a3df467d48ef99805f0a52fd8987ab.html