前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript设计模式之命令模式

JavaScript设计模式之命令模式

作者头像
用户6167509
发布2019-09-04 10:27:02
4590
发布2019-09-04 10:27:02
举报
文章被收录于专栏:一路向前端一路向前端

命令模式是将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

主要解决的问题:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

命令模式的结构

Command:定义命令的接口,声明执行的方法。

ConcreteCommand:命令接口实现对象,是"虚"的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。

Receiver:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。

Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

Client:创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。

命令模式不一定具备上面的所有结构,简单的命令模式可以只有命令的发布者和命令的接收者,这种简单命令模式中,接受者和执行者是同一个对象。比如:小明的妈妈告诉小明说:“今天中午妈妈会很忙,没时间出去买菜。你放学回来的时候,顺便帮妈妈把菜买回来”,在这个例子中,命令的发布者是小明的妈妈,小明是命令的接收者和执行者:

代码语言:javascript
复制
//发布命令者
var Command = function(receiver){
    this.receiver = receiver;
};
            
Command.prototype.execute = function(){
    this.receiver.action();
};
            
//命令的执行者和接受者
var Receiver = function(name){
    this.name = name;
};
            
Receiver.prototype.action = function(){
    alert(this.name + "中午放学后顺便把菜买了!");
};
            
var xiaoming = new Receiver("小明");
var xiaomingmama = new Command(xiaoming);
            
xiaomingmama.execute();

命令模式例子——菜单命令

现在页面中有三个button元素和一个菜单程序界面,这三个button元素的作用是:当用户点击时,他们分别会执行“刷新菜单”、“添加子菜单”和“删除子菜单”这三个功能。如果由一个程序员来完成这个功能就非常的简单了,因为他非常清楚那个按钮对应那个功能。但是如果实在一个分工很细致的团队里就不是这样了,例如一个人负责写html和css样式布局,另一个人负责写js,他们两个人同时进行各自的工作。在这种情况下,负责写js的那个人就很难确定哪个按钮对应哪个功能了。我们回想一下命令模式的使用场景:

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。

我们发现这种情况非常符合使用命令模式。

HTML按钮结构:

代码语言:javascript
复制
<button class="ref">刷新菜单</button>
<button class="add">添加子菜单</button>
<button class="del">删除子菜单</button>

首先我们对功能进行封装,将三个功能分别封装到Menu和SubMenu两个对象中:

代码语言:javascript
复制
//菜单对象
var Menu = {
    refresh: function(){
        console.log("刷新菜单");
    }
};
            
//子菜单
var SubMenu = {

    add: function(){
         console.log('增加子菜单');
    },
                
    del: function(){
         console.log('删除子菜单');
    }
};

然后封装三个功能调用的命令:

代码语言:javascript
复制
//封装刷新菜单命令
var refreshMenuCommand = function(receiver){
    this.receiver = receiver;
}
            
refreshMenuCommand.prototype.execute = function(){
    this.receiver.refresh();
}
            
//封装添加子菜单命令
var addSubMenuCommand = function(receiver){
    this.receiver = receiver;
}
            
addSubMenuCommand.prototype.execute = function(){
    this.receiver.add();
}
            
//封装删除子菜单命令
var delSubMenuCommand = function(receiver){
    this.receiver = receiver;
}
            
delSubMenuCommand.prototype.execute = function(){
    this.receiver.del();
}

在然后是封装设置命令函数:

代码语言:javascript
复制
//设置命令函数
function setCommand(btn, command){
    btn.addEventListener("click", function(){
        command.execute();
    })
};

最后是客户端(client)的调用:

代码语言:javascript
复制
//client客户调用
var refreshCommand = new refreshMenuCommand(Menu);
var addCommand = new addSubMenuCommand(SubMenu);
var delCommand = new delSubMenuCommand(SubMenu);
            
var btn = document.querySelectorAll("button");
            
setCommand(btn[0], refreshCommand);
setCommand(btn[1], addCommand);
setCommand(btn[2], delCommand);

JavaScript中的命令模式:

大家细看上面菜单的例子,会发现实现一个这么简单的功能,竟然弄得代码这么复杂难懂。即使不用什么模式,用下面几行代码就可以实现相同的功能:

代码语言:javascript
复制
//菜单对象
var Menu = {
    refresh: function(){
        console.log("刷新菜单");
    }
};
            
//子菜单
var SubMenu = {

    add: function(){
         console.log('增加子菜单');
    },
                
    del: function(){
         console.log('删除子菜单');
    }
};

//事件绑定函数
function addEvent(dom, fn, Capture){
    dom.addEventListener("click", fn, !!Capture);
};

var btn = document.querySelectorAll("button");
            
addEvent(btn[0], Menu.refresh);
addEvent(btn[1], SubMenu.add);
addEvent(btn[2], SubMenu.del);

这就是JavaScript语言中的命令模式,在JavaScript语言中函数是一等对象,它可以作为一个参数传递到函数内部去执行。所以在JavaScript这门语言中,命令模式和策略模式一样是JavaScript这门语言的天赋(生来即具有的属性)或者是隐性模式。命令模式其实就是回调函数一个面向对象的替代品,在JavaScript中命令模式和策略模式一样依赖回调函数实现,使用起来也更简单、更便捷。但有些时候这会成为一种缺点,因为他无法执行撤销操作,所以在实现撤销操作时,我们最好还是使用命令对象的execute方法为好。

撤销操作的实例

现在页面中有一个元素,有两个按钮,其中一个按钮点击时元素会往右移动一段距离,另一个按钮是撤销上一个移动操作。用命令模式实现这个功能,代码如下:

HTML结构:

代码语言:javascript
复制
<div class="demo">
    <button class="move">移动</button>
    <button class="undo">撤销</button>
    <div class="target" style="left:0"></div>
</div>

CSS样式:

代码语言:javascript
复制
.demo{
    width:100%;
    height:100px;
    position:relative;
}
            
.target{
    width:50px;
    height:50px;
    position:absolute;
    bottom:0;
    background-color:red;
}

js代码:

代码语言:javascript
复制
//移动对象
var Animate = function(dom){
    this.dom = dom;
    var self = this;
                
    //移动函数
    this.move = function(){
        var left = parseInt(self.dom.style.left);
        self.dom.style.left = left + 10 + 'px';
    };
                
    //取消移动函数
    this.undo = function(){
        var left = parseInt(self.dom.style.left);
        self.dom.style.left = left - 10 + 'px';
    };
};
            
//命令对象
var Command = function(receiver){
    var self = this;
                
    this.receiver = receiver;
    this.count = 0;  //记录执行命令的次数
                
                
    //执行命令
    this.execute = function(){
        self.receiver.move();
        self.count+= 1;
    };
                
    //撤销命令
    this.unexecute = function(){
        if(self.count === 0) return;
        self.receiver.undo();
        self.count-= 1;
    }
};

            
//设置命令函数
function addEvent(dom, fn, Capture){
    dom.addEventListener("click", fn, !!Capture);
};

//client调用
var dom = document.querySelector(".target");
var moveBtn = document.querySelector(".move");
var unmoveBtn = document.querySelector(".undo");

var m = new Animate(dom);
var c = new Command(m);
            
addEvent(moveBtn, c.execute);
addEvent(unmoveBtn, c.unexecute);

命令模式的优缺点:

优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。

缺点:使用命令模式可能会导致某些系统有过多的具体命令类。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-03-13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档