您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關JavaScript中怎么實現策略模式,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
策略模式:定義了一系列家族算法,并對每一種算法單獨封裝起來,讓算法之間可以相互替換,獨立于使用算法的客戶。
通常我并不會記得“牛頓***定律”的具體內容,所以我也難保證我會對這個定義記得多久……用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層。
看完上述內容,你們對JavaScript中怎么實現策略模式有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。