JavaScript 设计模式 命令模式
命令模式
命令模式是一种封装方法调用的方式 。命令模式与普通的函数有所不同,它可以用来对方法调用进行参数化处理和传递,经过这样处理过的方法调用可以在任何需要的时候执行。
命令模式可以消除调用操作的对象和实现操作的对象之间的耦合 ,这为各种具体的类换来了极大的灵活性。
命令模式的结构:最简形式的命令对象是一个操作和用以调用这个操作的对象的结合体。所有的命令对象都有一个执行操作,其用途就是调用命令对象所绑定的操作,在大多数命令对象中,这个操作是名为 execute
或 run
的方法。使用同样接口的所有命令对象都可以被同等对待,并且可以随意互换。
命令模式使用接口:目的为了检测命令对象是否实现了相应的操作
客户、调用者、接受者:客户创建命令,调用这个执行命令,接受者在命令执行时进行相应的操作
//命令模式是一种封装方法调用的方式
//命令模式的目的:把调用命令的调用者 和 执行命令的接受者
//要执行一件事情:1(命令 1) 2(命令 2) 3(命令 3)
//命令模式 必须实现接口 (execute)
//命令模式分类:简单命令模式、复杂命令模式(事物)、用闭包去封装命令模式(更加灵活的调用命令)
var CommandInterface = new BH.Interface('CommandInterface', ['execute']);
//有一个按钮 点击按钮 触发几个元素去执行一些动画效果
//2 个命令 (1 :start) (2 : stop )
//命令模式的主要概念: 调用者只需要执行相应的命令即可,不需要去关系命令到底怎么执行的
// 执行者它才真正的去处理这条命令的内部实现
/*
var StartCommod = function(obj){
this.ad = obj;
};
StartCommod.prototype.execute = function(){
this.ad.start();
};
var StopCommod = function(obj){
this.ad = obj;
};
StopCommod.prototype.execute = function(){
this.ad.stop();
};
var startCommand = new StartCommod(obj);
startCommand.execute();
var stopCommand = new StartCommod(obj);
stopCommand.execute();
*/
//闭包命令模式:
/*
function MakeStart(obj){
return function(){
obj.start();
};
};
function MakeStop(obj){
return function(){
obj.stop();
};
}
var startCommand = new MakeStart(obj);
startCommand(); //命令开启了
var stopCommand = new MakeStop(obj);
stopCommand(); //命令结束了
*/
// 客户、调用者、接受者
// 客户创建命令 ,调用这个执行命令 ,接受者在命令执行时进行相应的操作
//命令模式是一种封装方法调用的方式
//命令模式的目的:把调用命令的调用者 和 执行命令的接受者
//要执行一件事情:1(命令 1) 2(命令 2) 3(命令 3)
//命令模式 必须实现接口 (execute)
//命令模式分类:简单命令模式、复杂命令模式(事物)、用闭包去封装命令模式(更加灵活的调用命令)
// 客户、调用者、接受者
// 客户创建命令 ,调用这个执行命令 ,接受者在命令执行时进行相应的操作
//用命令模式实现简单的小游戏
// 页面上有 4 个按钮(up、down、left、right) 目标对象(元素 div..) 还有一个关键的按钮(回退按钮)
// 我现在有一个元素 up、down、left、right4 个命令 ,应该有一个集合去记录所有的命令 相应的执行命令
/**
* game implementation code
*/
// 有一个命令接口 俩个方法 一个是执行命令 还有一个是回退命令
var ReversibleCommandInterface = new BH.Interface('ReversibleCommandInterface',['execute','undo']);
//命令对象的类 参数传递的是接受者(cursor)
// 把命令对象叫做调用者
var MoveUp = function(cursor){
this.cursor = cursor;
};
MoveUp.prototype = {
constructor:MoveUp ,
execute:function(){
//真正的接受者调用自己的 move 方法(x 轴,y 轴)
this.cursor.move(0,-10);
},
undo:function(){
this.cursor.move(0,10);
}
};
var MoveDown = function(cursor){
this.cursor = cursor;
};
MoveDown.prototype = {
constructor:MoveDown ,
execute:function(){
//真正的接受者调用自己的 move 方法(x 轴,y 轴)
this.cursor.move(0,10);
},
undo:function(){
this.cursor.move(0,-10);
}
};
var MoveLeft = function(cursor){
this.cursor = cursor;
};
MoveLeft.prototype = {
constructor:MoveLeft ,
execute:function(){
//真正的接受者调用自己的 move 方法(x 轴,y 轴)
this.cursor.move(-10,0);
},
undo:function(){
this.cursor.move(10,0);
}
};
var MoveRight = function(cursor){
this.cursor = cursor;
};
MoveRight.prototype = {
constructor:MoveRight ,
execute:function(){
//真正的接受者调用自己的 move 方法(x 轴,y 轴)
this.cursor.move(10,0);
},
undo:function(){
this.cursor.move(-10,0);
}
};
//接受者(也就是操作具体方法的对象) // HTML5 + ECMA5
//Cursor
var Cursor = function(width,height,parent){
//宽高代表外层 div (canvas 画布)
this.width = width ;
this.height = height;
//移动的元素的具体位置
this.position = {
x: width/2,
y: height/2
};
// HTML5 新特性 : canvas(画布的意义)
//创建一个画布,定义画布的宽高
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
parent.appendChild(this.canvas);
//canvas 上下文元素(画布的核心对象)
this.ctx = this.canvas.getContext('2d');
this.ctx.fillStyle = 'red'; //填充颜色
this.move(0,0); //初始化位置
};
Cursor.prototype = {
constructor:Cursor ,
move:function(x,y){
this.position.x += x ;
this.position.y += y ;
this.ctx.clearRect(0,0,this.width,this.height); //clear this canvas 每次重画之前要清空画布
this.ctx.fillRect(this.position.x , this.position.y ,20,20);//画出方块
}
};
//应该有一个命令集合[数组]:目的就是为了当执行每一个命令之前 把该命令加入到集合中 (push、pop)
//命令对象在执行真的操作之前 应该把该命令加入到集合中 也就是在原始命令类上加一些新的特性 :特别适合(装饰者模式)
//使用装饰者模式 完成这件事情
//当前是一个装饰类 装饰命令对象类的实例 两个参数(原始的命令对象[被装饰者] , 命令集合[数组] )
var UndoDercorator = function(command,undoStack){
this.command = command ;
this.undoStack = undoStack;
};
UndoDercorator.prototype = {
constructor:UndoDercorator,
execute:function(){
//在执行真正命令之前 把命令加入命令集合中
this.undoStack.push(this.command);
this.command.execute();
},
undo:function(){
this.command.undo();
}
};
//完善一下 HTML 元素即可(四个按钮[命令按钮]、回退按钮)
// 命令按钮类
var CommandButton = function(label , command , parent){
//检验接口
BH.Interface.ensureImplements(command,ReversibleCommandInterface);
//实例化按钮 并放到父元素上
this.element = document.createElement('button');
this.element.innerHTML = label ;
parent.appendChild(this.element);
//添加事件
BH.EventUtil.addHandler(this.element,'click',function(){
command.execute(); //执行相应的命令
});
};
//回退按钮类
var UndoButton = function(label, parent , undoStack){
this.element = document.createElement('button');
this.element.innerHTML = label ;
parent.appendChild(this.element);
//添加事件
BH.EventUtil.addHandler(this.element,'click',function(){
if(undoStack.length === 0 ){
alert('已经没有命令了,是最后一步回退操作!');
return;
}
// 把最后一次命令拿出来 然后直接执行回退操作
var lastCommand = undoStack.pop();
lastCommand.undo();
});
}
window.onload= function(){
var body = document.getElementsByTagName('body')[0];
var cursor = new Cursor(400,400,body); //接受者对象实例化出来了
var undoStack = [] ; //命令集合
//客户:创建命令
var upCommand = new UndoDercorator(new MoveUp(cursor),undoStack);
var downCommand = new UndoDercorator(new MoveDown(cursor),undoStack);
var leftCommand = new UndoDercorator(new MoveLeft(cursor),undoStack);
var rightCommand = new UndoDercorator(new MoveRight(cursor),undoStack);
var upbtn = new CommandButton('up',upCommand,body);
var downbtn = new CommandButton('down',downCommand,body);
var leftbtn = new CommandButton('left',leftCommand,body);
var rightbtn = new CommandButton('right',rightCommand,body);
var undobtn = new UndoButton('undo',body,undoStack);
};
//基于上一个 demo 做一个命令历史留痕的效果
/**
*
*
* N 次命令:up up up left left down down
把这 7 次命令 存到 命令集合里
[up up up left left]
当你点击回退按钮的时候 原命令集合.pop();
就是从原始位置开始执行命令集合
这次画的不是图形:画的是数显:
html: lineTo 画线
fillStyle:给一个图形填充颜色的
strockStyle:给一个线去填充颜色的
现在咱们是有历史路径的了:
开始路径的方法 beginPath
确定位置:moveTo 移动的位置
画线的方法:(描边:strock 方法)
*/
/**
* game implementation code
*/
// 有一个命令接口 俩个方法 一个是执行命令 还有一个是回退命令
// 接口中不需要 undo 的方法 : 因为现在是重画集合的命令
var ReversibleCommandInterface = new BH.Interface('ReversibleCommandInterface',['execute']);
//命令对象的类 参数传递的是接受者(cursor)
// 把命令对象叫做调用者
// 4 个命令类中 :把原来的 undo 方法都去掉
var MoveUp = function(cursor){
this.cursor = cursor;
};
MoveUp.prototype = {
constructor:MoveUp ,
execute:function(){
//真正的接受者调用自己的 move 方法(x 轴,y 轴)
this.cursor.move(0,-10);
}
};
var MoveDown = function(cursor){
this.cursor = cursor;
};
MoveDown.prototype = {
constructor:MoveDown ,
execute:function(){
//真正的接受者调用自己的 move 方法(x 轴,y 轴)
this.cursor.move(0,10);
}
};
var MoveLeft = function(cursor){
this.cursor = cursor;
};
MoveLeft.prototype = {
constructor:MoveLeft ,
execute:function(){
//真正的接受者调用自己的 move 方法(x 轴,y 轴)
this.cursor.move(-10,0);
}
};
var MoveRight = function(cursor){
this.cursor = cursor;
};
MoveRight.prototype = {
constructor:MoveRight ,
execute:function(){
//真正的接受者调用自己的 move 方法(x 轴,y 轴)
this.cursor.move(10,0);
}
};
//接受者(也就是操作具体方法的对象) // HTML5 + ECMA5
//Cursor
// 主要修改接受者对象类
var Cursor = function(width,height,parent){
//宽高代表外层 div (canvas 画布)
this.width = width ;
this.height = height;
//在内部加一个属性(命令集合)
this.commandStack = [] ;
// HTML5 新特性 : canvas(画布的意义)
//创建一个画布,定义画布的宽高
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
parent.appendChild(this.canvas);
//canvas 上下文元素(画布的核心对象)
this.ctx = this.canvas.getContext('2d');
//这个是描绘线时所用的属性
this.ctx.strokeStyle = 'red';
this.move(0,0); //初始化位置
};
Cursor.prototype = {
constructor:Cursor ,
// move 只需要把 当前的命令对象方法 命令集合
move:function(x,y){
var that = this ;
//其实 commandStack 保留的都是函数类型
//在执行命令之前 线保留画线的函数
this.commandStack.push(function(){that.lineTo(x,y);});
//执行命令
this.executeCommands();
},
// lineTof 方法 才是真的画线的方法
lineTo:function(x,y){
this.position.x += x ;
this.position.y += y ;
//真正画线的方法
this.ctx.lineTo(this.position.x,this.position.y);
},
// 真正执行命令集合的方法
executeCommands:function(){
//确定当前的原始位置
this.position = {
x: this.width/2,
y: this.height/2
};
//清空当前的画布
this.ctx.clearRect(0,0,this.width,this.height);
//开始执行绘画路径的方法:
this.ctx.beginPath();
//确定当前画笔的位置
this.ctx.moveTo(this.position.x,this.position.y);
//循环遍历 commandStack,每一个元素都是一个函数 都可以执行
for(var i = 0 ; i < this.commandStack.length;i++){
this.commandStack[i](); //执行以前的命令
}
//描边方法
this.ctx.stroke();
},
//回退命令的方法
undo:function(){
//移除最后一次命令函数即可
this.commandStack.pop();
//调用执行方法 重新画出 commandStack 剩余的命令
this.executeCommands();
}
};
//装饰者不需要了 因为 命令集合已经在 接受者(cursor) 类里了(内置属性)
//完善一下 HTML 元素即可(四个按钮[命令按钮]、回退按钮)
// 命令按钮类
var CommandButton = function(label , command , parent){
//检验接口
BH.Interface.ensureImplements(command,ReversibleCommandInterface);
//实例化按钮 并放到父元素上
this.element = document.createElement('button');
this.element.innerHTML = label ;
parent.appendChild(this.element);
//添加事件
BH.EventUtil.addHandler(this.element,'click',function(){
command.execute(); //执行相应的命令
});
};
//回退按钮类
//参数变化 :传递的第三个参数 cursor 接受者
var UndoButton = function(label, parent , cursor){
this.element = document.createElement('button');
this.element.innerHTML = label ;
parent.appendChild(this.element);
//添加事件
BH.EventUtil.addHandler(this.element,'click',function(){
//直接调用接受者的回退方法即可
cursor.undo();
});
}
window.onload= function(){
var body = document.getElementsByTagName('body')[0];
var cursor = new Cursor(400,400,body);
//客户 直接实例化 命令对象实例
var upCommand = new MoveUp(cursor);
var downCommand = new MoveDown(cursor);
var leftCommand = new MoveLeft(cursor);
var rightCommand = new MoveRight(cursor);
var upButton = new CommandButton('UP',upCommand ,body);
var downButton = new CommandButton('DOWN',downCommand ,body);
var leftButton = new CommandButton('LEFT',leftCommand ,body);
var rightButton = new CommandButton('RIGHT',rightCommand ,body);
//注意第三个参数变成接受者了:cursor
var undoButton = new UndoButton('UNDO',body,cursor);
};
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: 关于 Git Issue
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论