设计模式之命令模式

2021-08-22  本文已影响0人  zhao_ran

命令模式中的命令(command)指的是一个执行某些特定事情的指令。

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

拿订餐来说,客人需要向厨师发送请求,但是完全不知道这些厨师的名字和联系方式,也不知道厨师炒菜的方式和步骤。 命令模式把客人订餐的请求封装成 command 对象,也就是订餐中的订单对象。这个对象可以在程序中被四处传递,就像订单可以从服务员手中传到厨师的手中。这样一来,客人不需要知道厨师的名字,从而解开了请求调用者和请求接收者之间的耦合关系。

命令模式的例子——菜单程序

在大型项目开发中,这是很正常的分工。对于绘制按钮的程序员来说,他完全不知道某个按钮未来将用来做什么,可能用来刷新菜单界面,也可能用来增加一些子菜单,他只知道点击这个按钮会发生某些事情。那么当完成这个按钮的绘制之后,应该如何给它绑定 onclick 事件呢?

回想一下命令模式的应用场景:

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

我们很快可以找到在这里运用命令模式的理由:点击了按钮之后,必须向某些负责具体行为的对象发送请求,这些对象就是请求的接收者。但是目前并不知道接收者是什么对象,也不知道接收者究竟会做什么。此时我们需要借助命令对象的帮助,以便解开按钮和负责具体行为对象之间的耦合。

var button1 = document.getElementById( 'button1' );
var button2 = document.getElementById( 'button2' );
var button3 = document.getElementById( 'button3' );

var setCommand = function(button, command){
    button.onclick = function(){
        command.execute();
    }
}

var MenuBar = {
    refresh: function(){
        console.log( '刷新菜单目录' );
    }
};
var SubMenu = {
    add: function(){
        console.log( '增加子菜单' );
    },
    del: function(){
        console.log( '删除子菜单' );
    }
};

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();
}

var refreshMenuCommand = new RefreshMenuCommand( MenuBar );
var addSubMenuCommand = new AddSubMenuCommand( SubMenu );
var delSubMenuCommand = new DelSubMenuCommand( SubMenu );

setCommand(button1, refreshMenuCommand);
setCommand(button2, addSubMenuCommand);
setCommand(button3, delSubMenuCommand);
JavaScript 中的命令模式

也许我们会感到很奇怪,所谓的命令模式,看起来就是给对象的某个方法取了 execute 的名字。引入 command 对象和receiver 这两个无中生有的角色无非是把简单的事情复杂化了,即使不用什么模式,用下面寥寥几行代码就可以实现相同的功能:

var bindClick = function( button, func ){
    button.onclick = func;
};
var MenuBar = {
    refresh: function(){
        console.log( '刷新菜单界面' );
    }
};
var SubMenu = {
    add: function(){
        console.log( '增加子菜单' );
    },
    del: function(){
        console.log( '删除子菜单' );
    }
};
bindClick( button1, MenuBar.refresh );

命令模式将过程式的请求调用封装在 command对象的execute方法里,通过封装方法调用,我们可以把运算块包装成形。command对象可以被四处传递,所以在调用命令的时候,客户(Client)不需要关心事情是如何进行的。

命令模式的由来,其实是回调( callback )函数的一个面向对象的替代品。

JavaScript作为将函数作为一等对象的语言,跟策略模式一样,命令模式也早已融入到了JavaScript语言之中。运算块不一定要封装在 command.execute方法中,也可以封装在普通函数中。函数作为一等对象,本身就可以被四处传递。即使我们依然需要请求“接收者”,那也未必使用面向对象的方式,闭包可以完成同样的功能。

在面向对象设计中,命令模式的接收者被当成command对象的属性保存起来,同时约定执行命令的操作调用command.execute方法。在使用闭包的命令模式实现中,接收者被封闭在闭包产生的环境中,执行命令的操作可以更加简单,仅仅执行回调函数即可。无论接收者被保存为对象的属性,还是被封闭在闭包产生的环境中,在将来执行命令的时候,接收者都能被顺利访问。用闭包实现的命令模式如下代码所示:

如果想更明确地表达当前正在使用命令模式,或者除了执行命令之外,将来有可能还要提供撤销命令等操作。那我们最好还是把执行函数改为调用 execute方法:

var button1 = document.getElementById( 'button1' );

var setCommand = function(button, command){
    button.onclick = function(){
        command.execute();
    }
}

var MenuBar = {
    refresh: function(){
        console.log( '刷新菜单目录' );
    }
};

var RefreshMenuCommand = function(receiver){
    return {
        execute: function(){
            receiver.refresh();
        }
    }
}

var refreshMenuCommand = new RefreshMenuCommand( MenuBar );

setCommand(button1, refreshMenuCommand);
上一篇下一篇

猜你喜欢

热点阅读