JavaScript设计模式之策略模式(学习笔记)

在网上搜索“为什么MVC不是一种设计模式呢?”其中有解答:MVC其实是三个经典设计模式的演变:观察者模式(Observer)、策略模式(Strategy)、组合模式(Composite)。所以我今天选择学习策略模式。

策略模式:定义了一系列家族算法,并对每一种算法单独封装起来,让算法之间可以相互替换,独立于使用算法的客户。

通常我并不会记得“牛顿第一定律”的具体内容,所以我也难保证我会对这个定义记得多久……用FE经常见到的东西来举个例子说明一下:

$("div").animation({left: ‘50px‘},1000,‘easein‘);

$("div").animation({left: ‘50px‘},1000,‘linear‘);

$("div").animation({left: ‘50px‘},1000,‘swing‘);

//看最后三个关于动画效果的参数

//Jquery文档总提到easing(第三个参数):要使用的擦除效果的名称(需要插件支持).默认jQuery提供"linear" 和 "swing".

我们在对元素设置动画的缓动效果,实际就是策略模式的一种实现。这样的缓动算法跟我们使用Jquery的人来说没有直接关系,假如我的项目中某个动画需要一种新的算法效果,那么我们再去开发一个插件就好了。反之,如果Jquery没有提供这样一种插件机制,那针对需求变化难不成要去改动Jquery的源码吗?

在《大话设计模式》一书中,作者举例的是一个商场的收银系统,在实际操作中,商場可能因为“双11买一送一”、“满500立减50”、“中秋节全场11折”等活动而对最终的收费产生变化。如果哪一天商场突然倒闭,全场两元,这时候我们仅需要给软件系统增加一个所有商品价格变两元的插件算法(类)即可。

我先来模拟一下策略模式的基本代码形态:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        function ConcreteStrategyA(){
            this.AlgorithmInterface = function(){
                console.log("算法A");
            }
        }

        function ConcreteStrategyB(){
            this.AlgorithmInterface = function(){
                console.log("算法B");
            }
        }

        function ConcreteStrategyC(){
            this.AlgorithmInterface = function(){
                console.log("算法C");
            }
        }

        //Context,用一个createStrategy来配置,维护一个对Strategy对象的引用

        function Context(strategy){
            this.strategy = strategy;
            this.ContextInterface = function(){
                strategy.AlgorithmInterface();
            }

        }

        //应用
        var context1 = new Context(new ConcreteStrategyA());
        context1.ContextInterface();

        var context2 = new Context(new ConcreteStrategyB());
        context2.ContextInterface();

        var context3 = new Context(new ConcreteStrategyC());
        context3.ContextInterface();
    </script>
</body>
</html>

通常来说,具体的某一种算法必须保证实现了某一些接口或者继承某个抽象类,才不会发生类型错误,在javascript中去实现接口、抽象类、继承等特性要费一些周章,所以我这个例子是不严谨的,仅从最简单的实现方式着手。

具体实现一个商场收银系统:包括一个单独js文件,和一个具体的实现html文件

//因为要用到数值验证,所以...这里用的是jquery2.1里面的isNum
function isNum(obj){
    return obj - parseFloat(obj)>=0;
}
//算法A,没有活动,正常收费
function ConcreteStrategyA(){
    this.AlgorithmInterface = function(money){
        return money;
    }
}
//算法B,满300减100
function ConcreteStrategyB(MoneyCondition,MoneyReturn){
    this.MoneyCondition = MoneyCondition,
    this.MoneyReturn    = MoneyReturn;

    this.AlgorithmInterface = function(money){
        var result=money;
        if(money>=MoneyCondition){
            result = money - Math.floor(money/MoneyCondition)*MoneyReturn;
        }
        return result;
    }
}
//算法C,打折
function ConcreteStrategyC(moneyRebate){
    this.moneyRebate = moneyRebate;
    this.AlgorithmInterface = function(money){
        return money*this.moneyRebate;
    }
}

//Context,用一个createStrategy来配置,维护一个对Strategy对象的引用
//这里将算法相关的从客户端剥离出来,简单工厂模式
function Context(type){
    this.strategy = null;
    switch(type){
        case "a":
            this.strategy = new ConcreteStrategyA();
            break;
        case "b":
            this.strategy = new ConcreteStrategyB("300","100");
            break;
        case "c":
            this.strategy = new ConcreteStrategyC("0.8");
            break;
    }

    this.ContextInterface = function(money){
        if(!isNum(money)){
            money = 0;
        }
        return this.strategy.AlgorithmInterface(money);
    }

}

HTML部分:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
        .block {
            padding:5px 0;
            border-bottom:1px solid #ccc;
        }
        .menu {margin:10px auto;text-align: center;}
    </style>
</head>
<body>
    <div class="block">
        <section class="product"><label>单价(RMB):<input type="text" class="tPrice" /></label><label>数量:<input type="text" class="tNum" /></label><label>计算方式:<select class="tAlg"><option value="a">正常收费</option><option value="b">满300减100</option><option value="c">打8折</option></select></label><label>合计:<input type="text" class="tMoney" /></label></section>
    </div>
    <div class="menu">
        <input type="button" id="addBtn" value="增加一个" />
    </div>
    <div>
        <label>总价:<input type="text" id="total" readonly /></label>
    </div>
    <script type="text/javascript" src="strategy.js"></script>
    <script type="text/javascript">
        var tPrice = document.getElementsByClassName("tPrice"),
            tNum   = document.getElementsByClassName("tNum"),
            tAlg   = document.getElementsByClassName("tAlg"),
            tMoney = document.getElementsByClassName("tMoney"),
            total  = document.querySelector("#total");

        var addBtn = document.querySelector("#addBtn");
        addBtn.addEventListener("click",function(){
            var html = ‘<section class="product"><label>单价(RMB):<input type="text" class="tPrice" /></label><label>数量:<input type="text" class="tNum" /></label><label>计算方式:<select class="tAlg"><option value="a">正常收费</option><option value="b">满300减100</option><option value="c">打8折</option></select></label><label>合计:<input type="text" class="tMoney" /></label></section>‘;
            var div = document.createElement("div");
            div.className="block";
            div.innerHTML = html;
            this.parentNode.parentNode.insertBefore(div,this.parentNode);
        })

        function calculate(e){

            //根据事件对象判断事件源,获取同类元素中的位置
            var num = 0,className = e.target.className;
            switch(className){
                case "tPrice":
                    for(var i=tPrice.length-1;i>=0;i--){
                        if(tPrice[i]==e.target){
                            num = i;
                        }
                    }
                    break;
                case "tNum":
                    for(var i=tNum.length-1;i>=0;i--){
                        if(tNum[i]==e.target){
                            num = i;
                        }
                    }
                    break;
                case "tAlg":
                    for(var i=tAlg.length-1;i>=0;i--){
                        if(tAlg[i]==e.target){
                            num = i;
                        }
                    }
                    break;
                default:
                    return;
            }

            var context = new Context(tAlg[num].value);
            var money   = 0;
            var totalValue = 0;

            money = context.ContextInterface(tPrice[num].value*tNum[num].value);

            tMoney[num].value = money;

            for(var index=0,len=tMoney.length;index<len;index++){
                totalValue += tMoney[index].value*1;
            }
            total.value = totalValue;
        }

        //绑定DOM事件
        // tPrice[0].addEventListener(‘keyup‘,calculate,false);
        // tNum[0].addEventListener(‘keyup‘,calculate,false);
        // tAlg[0].addEventListener(‘change‘,calculate,false);

        document.addEventListener(‘keyup‘,calculate,false);
        document.addEventListener(‘change‘,calculate,false);
    </script>
</body>
</html>

最开始我对商品单价、数量、计算方式仅提供一个可操作的地方,这也是《大话设计模式》一书中产品的基本形态,考虑到更良好交互性,我增加了一个按钮,可以增加更多行。这带来的一点小问题就是:起初我只需要为几个元素绑定事件即可,现在要对可能产生的更多元素绑定事件,所以我就选择了“事件代理”,获得发生事件的元素位置,改变同一行中的相应元素的值,对于总价,则总是遍历所有的单行总价相加。

BTW,在获取元素的时候使用了getElementsByClassName而没有使用querySelectorAll,是因为后者获取的不是一个动态集合。

接着我尝试将昨天学习的观察者设计模式与策略模式混合起来,起初我是这样做的....

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
        .block {
            padding:5px 0;
            border-bottom:1px solid #ccc;
        }
        .menu {margin:10px auto;text-align: center;}
    </style>
</head>
<body>
    <div class="block">
        <section class="product"><label>单价(RMB):<input type="text" class="tPrice" /></label><label>数量:<input type="text" class="tNum" /></label><label>计算方式:<select class="tAlg"><option value="a">正常收费</option><option value="b">满300减100</option><option value="c">打8折</option></select></label><label>合计:<input type="text" class="tMoney" /></label></section>
    </div>
    <div class="menu">
        <input type="button" id="addBtn" value="增加一个" />
    </div>
    <div>
        <label>总价:<input type="text" id="total" readonly /></label>
    </div>
    <script type="text/javascript" src="strategy.js"></script>
    <script type="text/javascript">

        //发布者
        function Publisher(obj){
            this.observers = [];
            var number = 0;

            this.getState=function(){
                return number;
            }
            this.setState = function(num){
                number = num;
                this.notice();
            }
        }
        Publisher.prototype.addOb=function(observer){
            var flag = false;
            for (var i = this.observers.length - 1; i >= 0; i--) {
                if(this.observers[i]===observer){
                    flag=true;
                }
            };
            if(!flag){
                this.observers.push(observer);
            }
            return this;
        }

        Publisher.prototype.removeOb=function(observer){
            var observers = this.observers;
            for (var i = 0; i < observers.length; i++) {
                if(observers[i]===observer){
                    observers.splice(i,1);
                }
            };
            return this;
        }
        Publisher.prototype.notice=function(){
            var observers = this.observers;
            for (var i = 0; i < observers.length; i++) {
                    observers[i].update(this.getState());
            };
        }

        //订阅者
        function Subscribe(obj){
            this.obj = obj;
            this.update = function(data){
                this.obj.value = data;
            };
        }

        //实际应用
        var tPrice = document.getElementsByClassName("tPrice"),
            tNum   = document.getElementsByClassName("tNum"),
            tAlg   = document.getElementsByClassName("tAlg");

        var pba = new Publisher(document);

        var oba = new Subscribe(document.getElementsByClassName("tMoney"));
        var obb = new Subscribe(document.querySelector("#total"));

        pba.addOb(oba).addOb(obb);

        oba.update = function(num){
            var context = new Context(tAlg[num].value);
            var money   = 0;

            money = context.ContextInterface(tPrice[num].value*tNum[num].value);

            this.obj[num].value = money;
        }
        obb.update = function(num){
            var totalValue = 0,
                tMoney = document.getElementsByClassName("tMoney");
            for(var index=0,len=tMoney.length;index<len;index++){
                totalValue += tMoney[index].value*1;
            }
            this.obj.value = totalValue;
        }

        var addBtn = document.querySelector("#addBtn");
        addBtn.addEventListener("click",function(){
            var html = ‘<section class="product"><label>单价(RMB):<input type="text" class="tPrice" /></label><label>数量:<input type="text" class="tNum" /></label><label>计算方式:<select class="tAlg"><option value="a">正常收费</option><option value="b">满300减100</option><option value="c">打8折</option></select></label><label>合计:<input type="text" class="tMoney" /></label></section>‘;
            var div = document.createElement("div");
            div.className="block";
            div.innerHTML = html;
            this.parentNode.parentNode.insertBefore(div,this.parentNode);
        })

        function calculate(e){

            //根据事件对象判断事件源,获取同类元素中的位置
            var num = 0,className = e.target.className;
            switch(className){
                case "tPrice":
                    for(var i=tPrice.length-1;i>=0;i--){
                        if(tPrice[i]==e.target){
                            num = i;
                        }
                    }
                    break;
                case "tNum":
                    for(var i=tNum.length-1;i>=0;i--){
                        if(tNum[i]==e.target){
                            num = i;
                        }
                    }
                    break;
                case "tAlg":
                    for(var i=tAlg.length-1;i>=0;i--){
                        if(tAlg[i]==e.target){
                            num = i;
                        }
                    }
                    break;
                default:
                    return;
            }
            pba.setState(num);
        }

        document.addEventListener(‘keyup‘,calculate,false);
        document.addEventListener(‘change‘,calculate,false);
    </script>
</body>
</html>

噢NO~~~~~~~

这尼玛有哪怕一点优雅的样子吗?反倒是徒添麻烦。。。不行,我既然学了这个,那么接下来就要学MVC了,MVC真的是长这样的吗???于是我又开始了度娘之旅。发现了这样一篇文章:JavaScript的MVC模式

这篇文章也是译文,好在我学过观察者模式了,耐着性子看吧~~~看着有点晕,这种观察者模式跟我之前学的不一样啊?

为了完全弄懂这篇文章的思路,我拿出笔纸开始画图,由于画工不好,字也写得差,我就不贴图了,弄一个对该文章整理思路后的总结:

我在之前学习观察者模式的时候,仅仅是对DOM元素进行了发布者与订阅者的区分,却不知道也没有思考过数据、视图与控制器这种结构中的发布者与订阅者区分,所以还是要多看看不同的案例。学习完这篇文章以后,我依葫芦画瓢对我这个“收银系统”也弄一下,但是我毕竟还没有学“组合模式”,所以我也不打算再写一个Controller,仅仅是Model和View之间加入观察者模式。最后的结果是这样的:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
        .block {
            padding:5px 0;
            border-bottom:1px solid #ccc;
        }
        .menu {margin:10px auto;text-align: center;}
    </style>
</head>
<body>
    <div class="block">
        <section class="product"><label>单价(RMB):<input type="text" class="tPrice" /></label><label>数量:<input type="text" class="tNum" /></label><label>计算方式:<select class="tAlg"><option value="a">正常收费</option><option value="b">满300减100</option><option value="c">打8折</option></select></label><label>合计:<input type="text" class="tMoney" /></label></section>
    </div>
    <div class="menu">
        <input type="button" id="addBtn" value="增加一个" />
    </div>
    <div>
        <label>总价:<input type="text" id="total" readonly /></label>
    </div>
    <script type="text/javascript" src="strategy.js"></script>
    <script type="text/javascript">

        //实现了观察者的Event类
        function Event(pub){
            this._pub = pub;
            this._listener = [];
        }
        Event.prototype = {
            attach: function(listener){
                this._listener.push(listener);
            },
            notify: function(num){
                for(var i=0;i<this._listener.length;i++){
                    this._listener[i](this._pub,num);
                }
            }
        }

        //模型
        function Model(data){
            this._data =new Array();
            this._data.push(data);
            this.itemAdded = new Event(this);
            this.itemChanged = new Event(this);
        }
        Model.prototype = {
            itemAdd : function(arr){
                this._data.push(arr);
                this.itemAdded.notify(this._data.length-1);
            },
            itemChange : function(arr,value){
                var a = arr[0], b=arr[1];
                this._data[a][b] = value;
                this.itemChanged.notify(a);
            }

        }
        //视图
        function View(model,ele){
            this._model = model;
            this._ele = ele;
            var that = this;

            //绑定模型侦听器
            this._model.itemAdded.attach(function(pub,num){
                 that.getTotal(pub,num);
            });
            this._model.itemChanged.attach(function(pub,num){
                 that.getTotal(pub,num);
            });

            //绑定DOM侦听器
            this._ele.eTarget.addEventListener(‘keyup‘,function(e){
                var target = e.target,
                    className = target.className;
                if(target.nodeName.toLowerCase()!=="input"){
                    return;
                }
                var elements = document.getElementsByClassName(className),
                    a,b;
                for(var i=elements.length-1;i>=0;i--){
                    if(elements[i]===target){
                        a = i;
                    }
                }
                switch(className){
                    case "tPrice":
                        b = 0;
                        break;
                    case "tNum":
                        b = 1;
                        break;
                    case "tMoney":
                        b = 3;
                        break;
                }
                if(!isNum(a)){
                    a = 0;
                }
                if(!isNum(b)){
                    b = 0;
                }
                that._model.itemChange([a,b],target.value);
            });
            this._ele.eTarget.addEventListener(‘change‘,function(e){
                var target = e.target,
                    className = target.className;
                if(target.nodeName.toLowerCase()!=="select"){
                    return;
                }
                var elements = document.getElementsByClassName(className),
                    a;
                for(var i=elements.length-1;i>=0;i--){
                    if(elements[i]===target){
                        a = i;
                    }
                }
                that._model.itemChange([a,2],target.value);
            });
            this._ele.addBtn.addEventListener(‘click‘,function(){
                var html = ‘<section class="product"><label>单价(RMB):<input type="text" class="tPrice" /></label><label>数量:<input type="text" class="tNum" /></label><label>计算方式:<select class="tAlg"><option value="a">正常收费</option><option value="b">满300减100</option><option value="c">打8折</option></select></label><label>合计:<input type="text" class="tMoney" /></label></section>‘;
                var div = document.createElement("div");
                div.className="block";
                div.innerHTML = html;
                this.parentNode.parentNode.insertBefore(div,this.parentNode);

                that._model.itemAdd([0,0,"a",0]);
            });
        }
        View.prototype.getTotal= function(pub,num){
            var price = this._model._data[num][0],
                number = this._model._data[num][1],
                alg = this._model._data[num][2],
                money = this._model._data[num][3];

            var context = new Context(alg);
            money = context.ContextInterface(price*number);
            this._model._data[num][3]=money;

            var total = 0;
            for(var i=0;i<this._model._data.length;i++){
                total += this._model._data[i][3]*1;
            }
            this._ele.money[num].value = money;
            this._ele.total.value = total;
        }

        var mmm = new Model([0,0,"a",0]),

            vvv = new View(mmm,{
                eTarget: document,
                addBtn: document.getElementById("addBtn"),
                money: document.getElementsByClassName("tMoney"),
                total: document.getElementById("total")
            });

    </script>
</body>
</html>

在形成上面的最终结果途中,在对数据进行计算并且将结果传递给Model时,我用了会触发观察者模式更新内容的函数,从而导致在一次计算以后又更新又计算又更新的无限循环中,改为直接对Model中的数据进行操作就没事了。而在我参考的文章中,View层是没有直接对Model进行操作,仅有访问数据的权限,把相关的Model操作放进了Controller层。

以上就是我今天的策略模式学习之路(顺带学了点MVC的相关知识),请各位道友多多指正。o(∩_∩)o

时间: 2024-10-12 21:32:01

JavaScript设计模式之策略模式(学习笔记)的相关文章

JavaScript设计模式之观察者模式(学习笔记)

设计模式(Design Pattern)对于软件开发来说其重要性不言而喻,代码可复用.可维护.可扩展一直都是软件工程中的追求!对于我一个学javascript的人来说,理解设计模式似乎有些困难,对仅切图.做少量交互效果的FE甚至可能不会用到,但是当你开始使用Angular/Backbone等框架的时候,就无法避免设计模式.MVC/MVVM这些东西了(反正我是伤脑筋). 我学设计模式是刚开始接触编程大概三个月的时候,看一本书<大话设计模式>,里面用C#语言来写,我很无语,因为强类型的编程语言对于

javascript设计模式:构造器模式学习一

javascript 设计模式1.简介javascript是一种弱类型语言,不过类可以通过函数模拟出来最常见的实现方法如下:function Car(model){ this.model = model; this.color = "red"; this.year = "2012"; this.getInfo = function(){ return this.model + " " + this.year; }}接下来我们可以使用上边定义Car

javascript设计模式之策略模式

数据验证之简易方式 // 举例 常见的验证表单字段经常是if else 或者 switch case var validater = { validate: function (value, type) { switch (type) { case 'isNonEmpty ': break; // NonEmpty 验证结果 case 'isNumber ': break; // Number 验证结果 case 'isAlphaNum ': break; // AlphaNum 验证结果 de

策略模式学习笔记

策略模式定义:将可变的部分从程序中抽象分离成算法接口,在该接口下分别封装一系列算法实现,并使它们可以相互替换,从而导致客户端程序独立于算法的改变. 策略模式区别于模板方法模式: - 策略模式强调整体算法,即将整个算法交由用户实现.(采用组合方式) - 模板方法模式强调部分算法,即整体算法不变情况下,部分子算法实现交由用户实现.(采用继承方式) 设计原则: - 找出不变部分抽象成接口(鸭子鸣叫行为),找出变化部分使用具体实现.(鸭子不叫,鸭子嘎嘎叫,鸭子唧唧叫) - 面向接口编程,而不是面向实现编

设计模式之策略模式(笔记)

策略模式:它定义了算法家族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户. 策略模式封装了变化,减少了各种算法类与使用算法类之间的耦合. 例如做一个商场收银软件,定义三种收费方式,正常收费,打8折,满300送100.这三种收费方式实际上就是具体的三种策略方式. 首先定义一个抽象算法类CashSuper,定义支持所有算法的公共接口 public abstract class CashSuper { //应付金额抽象方法 public abstract doub

javaScript设计模式---(工厂模式学习)

工厂模式(Factory)提供一个公用的接口来创建对象. 如有一个UI库,我们要创建某个UI组件的类型,不需要直接使用new 运算符或者通过另一个创建型构造函数创建这个组件,而是要求Factory对象创建一个新的组件.我们通知Factory需要什么样的对象(如按钮.面板),它会进行实例化,然后将它返回给我们. function Car (name, color) { this.name = name; this.color = color; } function Trunk (name, col

JavaScript设计模式与开发实践---读书笔记(5) 策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封转起来,并且使它们可以相互替换. JavaScript版本的策略模式: 奖金系统: var strategies = { "S": function(salary){ return salary*4; }, "A": function(salary){ return salary*3; }, "B": function(salary){ return salary*2; } }; var calc

理解javascript中的策略模式

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 使用策略模式的优点如下: 优点:1. 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句. 2. 策略模式提供了开放-封闭原则,使代码更容易理解和扩展. 3. 策略模式中的代码可以复用. 一:使用策略模式计算奖金: 下面的demo是我在书上看到的,但是没有关系,我们只是来理解下策略模式的使用而已,我们可以使用策略模式来计算奖金问题: 比如公司的年终奖是根据员工的工资和绩效来考核的,绩效为A的人,年终奖

WPF-MVVM模式学习笔记3——MVVM概念再次挖掘

通过上篇文章<WPF-MVVM模式学习笔记2--MVVM简单样例>中举了一个例子,我对MVVM大概有了一个比较浅显的意思.同时,看过前两篇文章的人,也知道我的这个系列的文章大多数来源于其他的博客,我其实只是起了一个汇总的作用,毕竟我也是在学习,肯定是要去网络上学习别人的笔记喽.本篇文章将以温故而知新的方式再次去理解MVVM,力求对MVVM的认识再深一个层次. 1.再看"M-V-VM" M:即Model,由现实世界抽象出来的模型. V:即View,视图,界面,该界面与用户输入