美文网首页
JavaScript设计模式 | 21-命令模式

JavaScript设计模式 | 21-命令模式

作者: 夏海峰 | 来源:发表于2019-03-02 16:07 被阅读12次

    1、模式定义

    命令模式,把请求与实现解耦,并封装成独立对象,从而使不同的请求对客户端的实现参数化。

    命令模式把请求模块和实现模块解耦。命令模式把创建模块的逻辑封装在一个对象里,并让这个对象提供一个参数化的请求接口,通过调用这个接口实现对该对象内部方法的调用。

    下面的代码结构,即是命令模式的结构框架:

    var ViewCommand = (function() {
        // 命令的集合
        var Action = {
            create: function() {},
            display: function() {}
        }
        // 命令模式对外的接口
        return function excute() {}
    })();
    

    2、命令模式 应用举例

    现有一个图片应用,有两个操作,一是创建图片DOM结构,二是把图片显示在页面上。下面我们使用命令模式来实现这个应用。命令模式对象对外的接口,接受两个重要参数,一是命令名称 msg.command,二是当前命令执行所需要的实参 msg.param

    注:使用 HTML结构模板,可以减小DOM操作拼凑页面带来的性能开销。封装方法 formatString用于把 HTML结构模板转化成真实的HTML字符串。

    var ViewCommand = (function() {
        // 定义两个 HTML结构模板
        var tpl = {
            // 用于展示图片结构的 HTML模板
            product: [
                '<div>',
                '<img src="{#src#}" />',
                '<p>{#text#}</p>',
                '</div>'
            ].join(''),
            // 用于展示标题结构的 HTML模板
            title: [
                '<div class="title">',
                '<div class="main">',
                '<h2>{#title#}</h2>',
                '<p>{#tips#}</p>',
                '</div>',
                '</div>'
            ].join('')
        };
        
        var html = '';
        // 封装:把模板字符转化成真实 HTML字符串
        function formatString(str, obj) {
            return str.replace(/\{#(\w+)#\}/g, function(match, key) {
                return obj[key];
            });
        }
        
        // 方法集合
        var Action = {
            // 创建模块视图
            create: function(data, view) {
                if (data.length) {
                    for(var i=0; i<data.length; i++) {
                        html += formatString(tpl[view], data[i]);
                    }
                } else {
                    html += formatString(tpl[view], data);
                }
            },
            // 视图展示
            display: function(container, data, view) {
                if(data) {
                    this.create(data, view);
                }
                document.getElementById(container).innerHTML = html;
                // 视图展示完成后,重置 html
                html = '';
            }
        }
        // 命令接口
        return function excute(msg) {
            // 转化成数组
            msg.param = Object.prototype.toString.call(msg.param) === "[object Array]" ? msg.param : [msg.param];
            // Action内部调用的方法引用了this,所以此处为了保证作用域this传入到Action中
            Action[msg.command].apply(Action, msg.param);
        }
    })();
    
    // 使用上述封装的 ViewCommand命令模式
    var productData = [
        {src: './1.png', text:'绽放的桃花'},
        {src: './2.png', text:'阳光下的温馨'},
        {src: './3.png', text:'镜头前的绿色'},
    ];
    
    var titleData = {title: '夏日里的一片温馨', tips: '暖暖的温情带给人们家的感受'};
    
    ViewCommand({command: 'display', param: ['title', titleData, 'title]});
    
    ViewCommand({command: 'create', param: ['product', productData, 'product']});
    
    图例

    3、命令模式 应用再举例

    Canvas应用有很多绘图API,每个API执行时所需要的参数也不尽相同。现在我们使用命令模式对Canvas进行封装,一方面隐藏了Canvas上下文(避免外界对其修改),另一方面让Canvas命令的调用更具一致。

    var CanvasCommand = (function() {
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        
        var Action = {
            fillStyle: function(c) {
                ctx.fillStyle = c;
            },
            fillRect: function(x, y, width, height) {
                ctx.fillRect(x, y, width, height);
            },
            strokeStyle: function(c) {
                ctx.strokeStyle = c;
            },
            strokeRect: function(x, y, width, height) {
                ctx.strokeRect(x, y, width, height);
            },
            fillText: function(text, x, y) {
                ctx.fillText(text, x, y);
            },
            beginPath: function() {
                ctx.beginPath();
            },
            moveTo: function(x, y) {
                ctx.moveTo(x, y);
            },
            lineTo: function(x, y) {
                ctx.lineTo(x, y);
            },
            arc: function(x, y, r, begin, end, dir) {
                ctx.arc(x, y, r, begin, end, dir);
            },
            fill: function() {
                ctx.fill();
            },
            stroke: function() {
                ctx.stroke();
            }
        };
        return {
            excute: function(msg) {
                // 如果没有命令,直接返回
                if (!msg) return;
                // 如果命令是一个数组
                if (msg.length) {
                    for(var i=0; i<msg.length; i++) {
                        arguments.callee(msg[i]);
                    }
                } else {
                    msg.param = Object.prototype.toString.call(msg.param) === "[object Array]" ? msg.param : [msg.param];
                    Action[msg.command].apply(Action, msg.param);
                }
            }
        }
    })();
    
    // 使用上述封装的 CanvasCommand 命令模式
    CanvasCommand.excute([
        {command: 'fillStyle', param: 'red'},
        {command: 'fillRect', param: [20, 20, 100, 100]}
    ]);
    

    4、小结

    命令模式把要执行的命令进行封装,解决了命令发起者和命令执行者之间的耦合。每条命令,本质上就是一个操作。命令的使用者不必了解命令执行者(命令对象)的命令接口是如何实现的,也不必了解命令是如何接受和执行的。所有的命令都被存储在命令对象中。

    在命令模式下,新的命令也很容易被加入到命令对象中去。命令的使用具有一致性,在一定程度上这简化了操作方法的使用。


    END 2019-03-02

    相关文章

      网友评论

          本文标题:JavaScript设计模式 | 21-命令模式

          本文链接:https://www.haomeiwen.com/subject/wyzeuqtx.html