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
JavaScript设计模式之策略模式(学习笔记)的更多相关文章
- 第二章 --- 关于Javascript 设计模式 之 策略模式
这一章节里面,我们会主要的针对JavaScript中的策略模式进行理解和学习 一.定义 策略模式: 定义一系列的算法,把他们封装起来,并且是他们可以相互替换. (这样的大的定义纲领,真的不好理解,特别 ...
- JavaScript设计模式之策略模式
所谓"条条道路通罗马",在现实中,为达到某种目的往往不是只有一种方法.比如挣钱养家:可以做点小生意,可以打分工,甚至还可以是偷.抢.赌等等各种手段.在程序语言设计中,也会遇到这种类 ...
- JavaScript设计模式(策略模式)
策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换.将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来 ...
- 再起航,我的学习笔记之JavaScript设计模式20(策略模式)
策略模式 策略模式(Strategy):将定义的一组算法封装起来,使其相互之间可以替换.封装的算法具有一定的独立性,不会随客户端变化而变化. 其实策略模式在我们生活中可应用的地方还是比较多的,比如在商 ...
- JavaScript设计模式之观察者模式(学习笔记)
设计模式(Design Pattern)对于软件开发来说其重要性不言而喻,代码可复用.可维护.可扩展一直都是软件工程中的追求!对于我一个学javascript的人来说,理解设计模式似乎有些困难,对仅切 ...
- JavaScript设计模式_02_策略模式
在程序设计中,我们常常遇到这种情况,要实现某一个功能我们有很多种算法可以实现.这些算法灵活多样,而且可以随意互相替换.这种解决方案就是所谓的策略模式. /* * pre:策略模式 * 示例:公司计算奖 ...
- javascript设计模式:构造器模式学习一
javascript 设计模式1.简介javascript是一种弱类型语言,不过类可以通过函数模拟出来最常见的实现方法如下:function Car(model){ this.model = mode ...
- JavaScript设计模式之策略模式【组合委托】
前言:语言只是工具,思想才是核心 今天要总结的是 策略模式 策略在开发中的应用非常广泛,所以也是非常常见且使用的设计模式. 在实际开发中,往往在实现一个功能时,有多种解决方案可行. 常见场景: 解压: ...
- javascript设计模式:策略模式
前言 策略模式有效利用组合.委托.多态等技术和思想,可以有效避免多重条件选择语句. 策略模式对开放-封闭原则提供了很好的支持,将算法封装在strategy中,使得他们易于切换.理解.扩展. 策略模式中 ...
随机推荐
- 编译内核出错:invalid option `abi=aapcs-linux' 解决办法
出现此问题的原因是由于kernel feature中选中了Use the ARM EABIto compile the kernel引起的,有两各解决办法: 1)换编译器为arm-linux-gcc ...
- 嵌套JSON 取出name与value
好久不用都忘了 mark下 $.each( { name: "John", lang: "JS" }, function(i, n){ alert( &qu ...
- ECommon.Dapper
ECommon.Dapper 轻量级的dapper扩展 我们都知道Dapper这个orm框架,但是我们也知道他的扩展目前没有特别好的,今天我就推荐一个轻量级的很方便使用的一个扩展叫做 ECommon. ...
- NSEnumerator用法小结
NSEnumerator 3)枚举 (NSEnumerator)遍历数组每个索引处的对象,你可以编写一个0到[array count]的循环,而NSEnumerator用来描述这种集合迭代运算的方 ...
- Javascript系列之js简介
JavaScript是一种网络客户端脚本语言,javascript有三个组成部分: 1)核心(ECMAScript)---描述了语言的基本语法和对象 2)文档对象模型(DOM)---描述了处理网页内容 ...
- nutch,hbase,zookeeper兼容性问题
nutch-2.1使用gora-0.2.1, gora-0.2.1使用hbase-0.90.4,hbase-0.90.4和hadoop-1.1.1不兼容,hbase-0.94.4和gora-0.2.1 ...
- BZOJ 3223 文艺平衡树
Description 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 ...
- [BZOJ 1303] [CQOI2009] 中位数图 【0.0】
题目链接:BZOJ - 1303 题目分析 首先,找到 b 的位置 Pos, 然后将数列中小于 b 的值赋为 -1 ,大于 b 的值赋为 1 . 从 b 向左扩展,不断算 Sum[i, b - 1] ...
- bzoj3709
首先明显会想到贪心对于那些怪物回血比耗血多的,我们显然应该先打耗血少的那些回血比耗血多的怎么办呢?由于不管怎么打(假设体力负数了还能打),最终体力是一定,我们从最终体力倒推,相当于先吃药掉血,打怪物回 ...
- ♫【Backbone】this
Backbone.js Event Binding MyView = Backbone.View.extend({ events: { 'click .item': 'handleClick' }, ...