在网上搜索“为什么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

转载于:https://www.cnblogs.com/gradolabs/p/4789717.html

JavaScript设计模式之策略模式(学习笔记)相关推荐

  1. 设计模式之策略模式学习笔记

    前言 我想大家都加班写过"业务代码",一大堆的if else,甚至if里面套if-代码不得已变得十分臃肿,对应的维护成本也有所增加.而策略模式,就是为了解决违反了开放封闭原则的这一 ...

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

    设计模式(Design Pattern)对于软件开发来说其重要性不言而喻,代码可复用.可维护.可扩展一直都是软件工程中的追求!对于我一个学javascript的人来说,理解设计模式似乎有些困难,对仅切 ...

  3. 设计模式之策略模式学习

    摘  要   策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户. --<HeadFirst设计模式> 一般 ...

  4. 设计模式之建造者模式学习笔记

    前言 建造者模式也叫生成器模式,是创建一个复杂对象的创建型模式,将此创建过程和部件解耦,使其构建过程和部件的表示分离开. 解释 建造者模式,说白了就是"攒机".比方说我想要定制一台 ...

  5. 设计模式之外观模式学习笔记

    简介 外观模式,也叫门面模式.隐藏了系统内部的复杂,向客户端提供了一个接口可以访问. 定义 一个子系统的外部和内部通信,必须通过一个统一的对象.该模式提供一个高层接口,使子系统更易使用. 结构图 角色 ...

  6. 设计模式之代理模式学习笔记

    前言 代理模式,也叫做委托模式.就好比请律师打官司一样,为其他对象提供一种代理,来控制对这个对象的访问. 结构图 接下来我们对以上结构图拆解,进行角色分析: 1.Subject:抽象主题类,声明真实主 ...

  7. 23种设计模式之工厂模式学习笔记

    什么是工厂模式? 工厂顾名思义就是创建产品,根据产品是具体产品还是具体工厂可分为简单工厂模式和工厂方法模式,根据工厂的抽象程度可分为工厂方法模式和抽象工厂模式.该模式用于封装和管理对象的创建,是一种创 ...

  8. 设计模式之模板模式学习笔记

    前言 相信大家都写过各种各样的文档,需求文档.设计文档.使用手册等等.为了提高复用性和灵活度,需要采用一系列的模板来应对. 定义 定义一个操作中的算法框架,将一些步骤延迟到子类中,使子类不改变一个算法 ...

  9. Spring源码学习笔记:经典设计模式之策略模式

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 0.策略模式(Strategy pattern) 指定义了算法家族,分别封装起来,让它们之间可以互相替换,此模 ...

最新文章

  1. PHP错误日志,解决不显示不记录日志文件等疑难杂症
  2. webuploader 怎么在react中_另辟蹊径搭建阅读React源码调试环境支持所有React版本细分文件断点调试...
  3. 详解const和#define
  4. Servlet获取URL地址
  5. 什么时候会触发这个策略呢?
  6. 新手学习Linux——rsync+shell脚本完成自动化备份
  7. 在印度8年的华为工程师,有很多话想说。
  8. 解决克隆clone github 仓库速度过慢的问题
  9. bzoj2839 集合计数
  10. jquery伪分页控件
  11. 计算机硬盘ssd,怎样判断电脑装的是不是固态硬盘
  12. dw新建html快捷键,DW软件新建一个html网页
  13. 内存核心频率、工作频率,等效频率、预读取技术详解
  14. 如何交叉编译fio并移植到ARM、IOT上
  15. 听说你还不会制作“GIF动图”,手把手包教会,这不就来了吗
  16. win10服务器系统要设置要密码是什么,云服务器win10系统初始密码
  17. JAVA系列:获取当天0点0分0秒(00:00:00),23点59分59秒(23:59:59)的方法
  18. C#往图片上面添加文字
  19. iOS音乐播放器详解(MusicPlayer1.0)
  20. 全国大学生数学建模竞赛——2017A题(学习笔记)

热门文章

  1. 3D点集之间计算转移矩阵,旋转R,转移T,新增缩放s (总结全面)
  2. 揭秘“水军”:千元制造一条10W+ 微博打榜豆瓣刷分无所不能
  3. 十万条评论告诉你,给《流浪地球》评1星的都是什么心态? | Alfred数据室
  4. 牛客竞赛小白试炼(20201205 怕npy的牛牛)
  5. 太简单!只学十分钟,Python菜鸟也能开发一个区块链客户端
  6. 深度学习系列错误笔记(一)之print(‘gpu‘,torch.cuda.is_available())输出gpu False
  7. 程序员的5个级别,你属于哪一个等级?
  8. LeetCode——字符串的最大公因子
  9. 纽约大学Yann LeCun深度学习
  10. 公务员考试行测资料分析技巧