JavaScript观察者模式的运用

观察者模式,是设计模式中的一种,具体含义和说明,这里就不写了,可以执行百度,这里主要讲解如何使用?如何改善现有的代码逻辑?如何解耦?等问题
一、最简单的例子

$("#test").on("click",function(e){
    alert("hi");
});

这是jQuery中的监听元素的点击事件,然后做出响应;其实这就是观察者模式的运用;监听某个事件,让其它模块来响应你,可能光这么说,感受不出它的好处,看下面的实例。
二、常规的模块间调用
用一个比较直接的例子说明,每个游戏中,都有生命值这个概念,当生命值为0时就结束游戏,假设有三个模块:Life-生命值管理模块,Animation-动画模块,Dead-死亡模块

//生命值管理模块
var Life = function(){
 
    //最大生命值
    var _max = 100;
    //当前生命值
    var _life = 100;
    //生命值变化接口
    function lifeChange(diff){
        //增加或者扣血
        _life += diff;
 
        //调用其它模块生命值变化接口
        Animation.lifeChange({life:_life,diff:diff});
        Dead.lifeChange({life:_life,diff:diff});
    }
 
    return {
        lifeChange : lifeChange
    }
}();
 
//动画模块,显示生命值变化动画
var Animation = function(){
 
    return {
        lifeChange : function(param){
 
            $("#life").animation({width:param.life + "px"});
        }
    };
}
 
//死亡模块,当生命值为空时结束游戏
var Dead = function(){
 
    return {
        lifeChange : function(param){
            if(param.life <= 0)return alert("game over");
        }
    }
}();

上面是简单的三个模块实现逻辑,大致思路为:
当生命值发生变化的时候,即有怪物砍了你一刀,调用了Life.lifeChange(-20)函数。
这个时候,生命值需要发生改变,_lift减掉20
然后需要让生命变化的过程有动画,调用了动画模块的Animation.lifeChange函数
之后要判断是否死亡了,调用了死亡模块的Dead.lifeChange函数
存在的问题
模块之间高度耦合,Life和Animation、Dead之间存在互相依赖的关系
如果需要增加一个新模块,比如满血模块,会增加攻击力等,那你又需要在Life里增加一次函数调用,耦合的模块又增加多一个
不灵活,无法在当前模块,比如:Dead,得知lifeChange的调用在哪?以及时机?后期维护变得头疼
思考?要是能像jQuery的点击事件监听那样就好了~~
三、自定义事件监控和调用
自己实现类似jQuery的监听和调用的逻辑,使得模块的耦合降低

//简单的事件监听和执行
var Proxy = function(){
 
    //保存所有代理
    var _Proxy = {};
 
    return {
        //监听代理
        onProxy : function(namespace,action,func){
 
            //创建命名空间,避免事件名冲突
            if(!Proxy[namespace])Proxy[namespace] = {};
            if(!Proxy[namespace][action]) Proxy[namespace][action] = [];
 
            //添加代理到队列中
            Proxy[namespace][action].push(func);
        },
        //执行代理
        execProxy : function(namespace,action,param){
 
            if(!Proxy[namespace])return false;
            if(!Proxy[namespace][action])return false;
 
            //遍历代理队列,执行函数
            for(var i=0;i<Proxy[namespace][action].length;i++){
 
                Proxy[namespace][action][i](param);
            }
        }
    };
}();

实现思路
需要在原本调用lifeChange的地方,执行代理,即示例中的Life.lifeChange函数中
需要在原本提供接口给外部调用的模块,增加监听,即示例中的Dead.lifeChange和Animation.lifeChange
修改如下

//生命值管理模块
var Life = function(){
 
    //最大生命值
    var _max = 100;
    //当前生命值
    var _life = 100;
    //生命值变化接口
    function lifeChange(diff){
        //增加或者扣血
        _life += diff;
        //执行生命值变量变化的事件代理
        Proxy.execProxy("Life","lifeChange",{life:_life,diff:diff});
    }
 
    return {
        lifeChange : lifeChange
    }
}();
 
//动画模块,显示生命值变化动画
var Animation = function(){
 
    function init(){
 
        //监听生命值变化
        Proxy.onProxy("Life","lifeChange",function(param){
 
            $("#life").animation({width:param.life + "px"});
        });
    }
    init();
}
 
//死亡模块,当生命值为空时结束游戏
var Dead = function(){
 
    function init(){
 
        //监听生命值变化
        Proxy.onProxy("Life","lifeChange",function(param){
             
            if(param.life <= 0)return alert("game over");
        });
    }
    init();
}();

实现逻辑
Life模块中,不再需要知道具体需要调用哪个模块的哪个函数了,只需要统一执行一次代理就可以了
Animation和Dead模块,不再需要提供接口给外部了,只需要监听lifeChange事件,就可以执行对应的逻辑
强制解耦三个模块之间的联系
即使增加多几个模块,也不需要修改Life模块,各自模块监听需要的事件即可
小结
设计模式,在很多时候能够帮助我们更好的管理和设计代码、模块等,善加利用,能够事半功倍


本文转载于FEG博客:http://feg.netease.com/archives/242.html

本文由 w3cmark_前端笔记 版权所有,转载时请注明出处。
注明出处格式:w3cmark (http://www.w3cmark.com/2016/491.html)

分享到:

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
关注w3cmark
微信公众号 w3cmark_com