• JavaScript设计模式之命令模式


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

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

    命令模式的结构

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

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

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

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

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

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

    //发布命令者
    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按钮结构:

    <button class="ref">刷新菜单</button>
    <button class="add">添加子菜单</button>
    <button class="del">删除子菜单</button>

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

    //菜单对象
    var Menu = {
        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();
    }

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

    //设置命令函数
    function setCommand(btn, command){
        btn.addEventListener("click", function(){
            command.execute();
        })
    };

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

    //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中的命令模式:

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

    //菜单对象
    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结构:

    <div class="demo">
        <button class="move">移动</button>
        <button class="undo">撤销</button>
        <div class="target" style="left:0"></div>
    </div>

    CSS样式:

    .demo{
        width:100%;
        height:100px;
        position:relative;
    }
                
    .target{
        width:50px;
        height:50px;
        position:absolute;
        bottom:0;
        background-color:red;
    }

    js代码:

    //移动对象
    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、新的命令可以很容易添加到系统中去。

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

  • 相关阅读:
    Ubuntu20 修改网卡名称
    单臂路由实现不同vlan间通信
    配置trunk和access
    基于端口划分vlan
    Zabbix5.0服务端部署
    搭建LAMP环境部署opensns微博网站
    搭建LAMP环境部署Ecshop电商网站
    Zabbix 监控过程详解
    Zabbix agent端 配置
    Zabbix 监控系统部署
  • 原文地址:https://www.cnblogs.com/jofun/p/8550724.html
Copyright © 2020-2023  润新知