设计模式:有助于提高代码的复用性和可维护性
假设有一个快餐店,而我是该餐厅店点餐服务员,那么我一天的工作量是这样的:当某位客人点餐或者打来订餐电话的时候,我会把他的需要写在清单上,交给厨房,客人不用关心是哪个厨师给他做饭,我们餐厅还可以满足客人需要定时服务。比如客人可能当前正在回家的路上,要求一个小时后再炒他的菜。只要订单还在,厨师就不会忘记。客人也可以很方便的打电话来撤销他的订单。另外如果有太多的客人点餐,厨房可以按照订单的顺序排队炒菜
这些记录这订单信息的清单,便是命令模式中的命令对象
命令模式最常用的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和接收者能够消除彼此之间的耦合关系
命令模式可以方便的给命令对象添加增加撤销操作,下面看一下这个例子
p标签里面的默认数字是0,可以通过添加按钮,让p标签里的数字递增
<div id="number">0</div>
<button id="add">+</button>
let number = document.getElementById('number')
let worker = {
//添加
add(){
oldValue = isNaN(number.innerHTML)?0:parseInt(number.innerHTML)
number.innerHTML = oldValue+1
}
}
//创建添加命令
class AddCommand{
constructor(receiver){
this.receiver = receiver
}
execute(){
this.receiver.add()
}
}
let addCommand = new AddCommand(worker)
document.getElementById('add').addEventListener('click',function(){
addCommand.execute()
})
接下来增加撤销按钮
<div id="number">0</div>
<button id="undo">undo</button>
撤销操作一般给命令对象添加一个undo的方法,在数字增加之前,先记录当前的数字,在执行undo方法的时候,再让数字回到之前
let worker = {
oldValue:0,
//添加
add(){
oldValue = isNaN(number.innerHTML)?0:parseInt(number.innerHTML)
worker.oldValue = oldValue //记录之前的数字
number.innerHTML = oldValue+1
},
//撤销
undo(){
number.innerHTML = worker.oldValue
}
}
//创建撤销命令
class UndoCommand{
constructor(receiver){
this.receiver = receiver
}
execute(){
this.receiver.undo()
}
}
let undoCommand = new UndoCommand(worker)
document.getElementById('undo').addEventListener('click',function(){
undoCommand.execute()
})
现在通过命令模式轻松的实现来撤销功能。如果用普通的方法来调用,也许每次都要记录数字,才能让它返回之前都数字。而命令模式中之前都数字已经作为command对象都属性保存起来来,所以只需要提供一个undo方法,并且在undo方法中让数字回到之前到就可以了
上面的撤销只可以撤销一步,很多时候,我们需要撤销一系列的命令。比如浏览器的前进和回退按钮。这样的话,我们把所以执行的命令都存到一个history列表中,切换对应都索引值,来拿到当前都数据,请看下列代码实现
<button id="redo">redo</button>
let number = document.getElementById('number')
let worker = {
history:[],
index:-1,
//添加
add(){
var oldValue = isNaN(number.innerHTML)?0:parseInt(number.innerHTML)
var newValue = oldValue+1
worker.history.push(newValue)
worker.index = worker.history.length - 1;
number.innerHTML = oldValue+1
},
//撤销
undo(){
if(worker.index>0){
worker.index--;
number.innerHTML = worker.history[worker.index]
}
},
//重做
redo(){
if(worker.history.length-1>worker.index){
worker.index++;
number.innerHTML = worker.history[worker.index]
}
}
}
//创建命令
class RedoCommand{
constructor(receiver){
this.receiver = receiver
}
execute(){
this.receiver.redo()
}
}
let redoCommand = new RedoCommand(worker)
document.getElementById('redo').addEventListener('click',function(){
redoCommand.execute()
})
然而,有时候我们无法顺利都利用redo回到之前都execute之前都状态,比如在Canvas画图都过程中,画布上都每一点,我们这些点之间画来n条曲线连接起来,当然这是用命令模式实现都。但是我们却很难为这里都命令对象定义一个擦除某条曲线都undo操作,因为在Canvas画图中,擦除一条线相对不容易实现
这时候最好都办法是先清除画布,然后把刚才执行多都命令全部中心执行一遍,者一点同样利用一个历史列表堆栈办到。记录命令日志,然后重复执行他们,这是是逆转不可逆命令都一个好办法