整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          前端開(kāi)發(fā),用英雄聯(lián)盟的方式講解JavaScript設(shè)

          前端開(kāi)發(fā),用英雄聯(lián)盟的方式講解JavaScript設(shè)計(jì)模式

          造函數(shù)模式

          簡(jiǎn)介

          在JavaScript里,構(gòu)造函數(shù)通常是認(rèn)為用來(lái)實(shí)現(xiàn)實(shí)例的特殊的構(gòu)造函數(shù)。通過(guò)new關(guān)鍵字來(lái)調(diào)用定義的構(gòu)造函數(shù),你可以告訴JavaScript你要?jiǎng)?chuàng)建一個(gè)新對(duì)象并且新對(duì)象的成員聲明都是構(gòu)造函數(shù)里定義的。在構(gòu)造函數(shù)內(nèi)部,this關(guān)鍵字引用的是新創(chuàng)建的對(duì)象。

          作為一個(gè)老聯(lián)盟fans,一定要親手實(shí)現(xiàn)一下設(shè)計(jì)模式也可以融會(huì)貫通。

          現(xiàn)在打算創(chuàng)建一個(gè)英雄聯(lián)盟對(duì)象,需要地圖,英雄,士兵,野怪,還有開(kāi)始游戲的按鈕。

          function LOL(maps, heros, soldier, monster) {
              this.maps=maps
              this.heros=heros
              this.soldier=soldier
              this.monster=monster
              this.start=function() {
                  return '地圖:' + this.maps + '\n對(duì)戰(zhàn)英雄:' + this.heros.join() + '\n小兵類型:' + this.soldier + '\n野怪:' + this.monster + '\n'
              }
          }
          
          
          var game1=new LOL('召喚師峽谷', ['影流之主', '詭術(shù)妖姬'], '超級(jí)兵', '紅buff')
          var game2=new LOL('大亂斗', ['影流之主', '詭術(shù)妖姬'], '超級(jí)兵', '紅buff')
          console.log(game1.start())
          console.log(game2.start())

          這樣寫代碼,每局游戲需要重新創(chuàng)建一個(gè)英雄聯(lián)盟實(shí)例,

          這樣使用構(gòu)造器,有多少個(gè)game就需要多少個(gè)start函數(shù)方法,如果共用一個(gè)start方法,可以節(jié)約很多內(nèi)存

          function LOL(maps, heros, soldier, monster) {
              this.maps=maps
              this.heros=heros
              this.soldier=soldier
              this.monster=monster
          }
          LOL.prototype.start=function() {
               return '地圖:' + this.maps + '\n對(duì)戰(zhàn)英雄:' + this.heros.join() + '\n小兵類型:' + this.soldier + '\n野怪:' + this.monster + '\n'
          }
          
          
          var game1=new LOL('召喚師峽谷', ['影流之主', '詭術(shù)妖姬'], '超級(jí)兵', '紅buff')
          var game2=new LOL('大亂斗', ['影流之主', '詭術(shù)妖姬'], '超級(jí)兵', '紅buff')
          
          console.log(game1.start())
          console.log(game2.start())

          如果讓start方法變成大家通用的就好了,因此把LOL.prototype.start改寫,這樣所以的LOL實(shí)例就可以共用一個(gè)方法,從原型鏈上繼承即可

          上面的方式可以節(jié)省內(nèi)存,start實(shí)例函數(shù)可以在所有LOL對(duì)象的實(shí)例中使用

          如果不使用new 也可以有其他方式創(chuàng)建對(duì)象

          function LOL(maps, heros, soldier, monster) {
              this.maps=maps
              this.heros=heros
              this.soldier=soldier
              this.monster=monster
              this.start=function() {
                  return '地圖:' + this.maps + '\n對(duì)戰(zhàn)英雄:' + this.heros.join() + '\n小兵類型:' + this.soldier + '\n野怪:' + this.monster + '\n'
              }
          }
          
          
          var game3=new Object();
          LOL.call(game3, "扭曲叢林", ['影流之主', '劍圣'], '遠(yuǎn)程兵', '大龍');
          console.log(game3.start())
          
          //也可以不使用new ,通過(guò)call方法在game3的作用域調(diào)用LOL

          這種方式雖然可以創(chuàng)建新的構(gòu)造函數(shù),但卻不能繼承LOL原型上的函數(shù)

          如果直接運(yùn)行LOL()函數(shù)(不使用new的情況下),由于this指向的是window對(duì)象,因此start方法會(huì)變成window.start()

          如果強(qiáng)制要求函數(shù)使用new 方法也可以如下創(chuàng)建:

          function LOL(maps, heros, soldier, monster) {
              if (!(this instanceof LOL)) {
                  return new LOL(maps, heros, soldier, monster);
              }
              this.maps=maps
              this.heros=heros
              this.soldier=soldier
              this.monster=monster
          }

          通過(guò)判斷this的instanceof,就可以知道究竟是來(lái)自new方法,還是說(shuō)是直接調(diào)用。如果是直接調(diào)用的話,判斷條件為true,還是會(huì)return一個(gè)新的實(shí)例。

          e.g:

          var s=new String("lol");
          var n=new Number(101);
          var b=new Boolean(true);
          // s n b返回的是實(shí)例對(duì)象
          //String{
          //  0: 'l',
          //  1: 'o',
          //  2: 'l'
          //  }
          
          //可以直接給變量賦值,或者不加new 關(guān)鍵詞

          外觀模式

          簡(jiǎn)介

          外觀模式最大的體現(xiàn)其實(shí)就是入口,比如init()函數(shù),把一些內(nèi)部的函數(shù)都放在這個(gè)門面之下,只需要調(diào)用這個(gè)門面函數(shù),其他亂七八糟的功能都可以實(shí)現(xiàn)。

          現(xiàn)在有一個(gè)英雄,叫做亞索,我希望給他一些配置,比如技能,衣服等


          function YaSuo() {
          }
          function Qskill (hero) {
              hero.prototype.Qskill=function() {
                  console.log('hasaki!!')
              }
          }
          function Wskill (hero) {
              hero.prototype.Wskill=function() {
                  console.log('風(fēng)墻')
              }
          }
          function Eskill (hero) {
              hero.prototype.Eskill=function() {
                  console.log('快樂(lè)')
              }
          }
          function Rskill (hero) {
              hero.prototype.Rskill=function() {
                  console.log('痛里唉該痛')
              }
          }
          function Skin (hero, skin) {
              hero.prototype.skin=skin
          }
          
          function CreateYasuo () {
              Qskill(YaSuo)
              Wskill(YaSuo)
              Eskill(YaSuo)
              Rskill(YaSuo)
              Skin(YaSuo, 'originSkin')
              return new YaSuo()
          }
          CreateYasuo()
          // 創(chuàng)建成功 外觀模式啟動(dòng)

          通過(guò)上面的代碼,成功創(chuàng)建了一個(gè)美麗的亞索。我們最后只需要了解,外觀模式不僅簡(jiǎn)化類中的接口,而且對(duì)接口與調(diào)用者也進(jìn)行了解耦。外觀模式經(jīng)常被認(rèn)為開(kāi)發(fā)者必備,它可以將一些復(fù)雜操作封裝起來(lái),并創(chuàng)建一個(gè)簡(jiǎn)單的接口用于調(diào)用

          說(shuō)白了就是用一個(gè)接口封裝其它的接口。

          外觀模式優(yōu)點(diǎn)就是易使用。缺點(diǎn)則是,當(dāng)連續(xù)使用外觀模式創(chuàng)建的接口時(shí),可能會(huì)產(chǎn)生性能問(wèn)題。

          e.g.


          var addMyEvent=function (el, ev, fn) {
              if (el.addEventListener) {
                  el.addEventListener(ev, fn, false);
              } else if (el.attachEvent) {
                  el.attachEvent('on' + ev, fn);
              } else {
                  el['on' + ev]=fn;
              }
          };

          這是最常見(jiàn)對(duì)監(jiān)聽(tīng)事件的處理,前端必會(huì)。其中的addMyEvent就是對(duì)其他三個(gè)接口的封裝,產(chǎn)生了一個(gè)門面,也就是外觀模式。

          代理模式

          簡(jiǎn)介

          其實(shí)代理模式我們生活中接觸的很多了。比如es6中的proxy對(duì)象,還有我們平時(shí)上網(wǎng)用的VPN。那其實(shí)代理模式,就是讓一個(gè)對(duì)象幫助其他的對(duì)象來(lái)做事。

          比如我現(xiàn)在想創(chuàng)建一個(gè)英雄,名字叫做卡莉斯塔,俗稱滑板鞋。這個(gè)英雄有個(gè)特點(diǎn),當(dāng)她放R技能的時(shí)候,會(huì)把一個(gè)對(duì)象拉過(guò)來(lái)到自己身邊幾秒,代理這個(gè)對(duì)象的走路行為,禁止他釋放技能等等,那這就要用到代理模式了。

          // 聲明走路動(dòng)作
          function Walk (hero) { // 代理期間執(zhí)行的操作
              return function() {console.log(hero + ' is walk')}
          }
          function Kalisita () { // proxy
              this.walk=Walk('Kalisita')
              this.Rskill=function(hero) { // 傳入要拉取的英雄
                  this.walk=function() {
                      Walk('Kalisita')() // 既需要自己走
                      hero.walk() // 還需要帶著人一起走
                  }
              }
          }
          function HeroA () { // 被代理走路的英雄
              this.walk=Walk('heroA')
          }
          
          var k=new Kalisita()
          var a=new HeroA()
          
          k.walk() // Kalisita is walk
          a.walk() // heroA is walk
          
          k.Rskill(a) // k把a(bǔ)的walk事件代理了, 現(xiàn)在k觸發(fā)walk的同時(shí),也會(huì)帶著a一起walk哦
          k.walk()
          // Kalisita is walk
          // heroA is walk

          代理模式主要用于幾點(diǎn),

          • 要獲取本來(lái)沒(méi)有的對(duì)對(duì)象操作的權(quán)限
          • 要獲取遠(yuǎn)程文件,需要代理模式作為跳板
          • 根據(jù)需要?jiǎng)?chuàng)建開(kāi)銷很大的對(duì)象,通過(guò)它來(lái)存放實(shí)例化需要很長(zhǎng)時(shí)間的真實(shí)對(duì)象,比如瀏覽器的渲染的時(shí)候先顯示問(wèn)題,而圖片可以慢慢顯示(就是通過(guò)虛擬代理代替了真實(shí)的圖片,此時(shí)虛擬代理保存了真實(shí)圖片的路徑和尺寸。
          • 只當(dāng)調(diào)用真實(shí)的對(duì)象時(shí),代理處理另外一些事情。例如C#里的垃圾回收,使用對(duì)象的時(shí)候會(huì)有引用次數(shù),如果對(duì)象沒(méi)有引用了,GC就可以回收它了。

          詳情可以參考《大話設(shè)計(jì)模式》

          而我們前端代碼中用的比較多的,應(yīng)該就是vue.js中對(duì)data中數(shù)據(jù)響應(yīng)式的代理。vue3中也將使用大量ES6支持的Proxy對(duì)象來(lái)改寫。

          e.g.

          通過(guò)代理,嘗試設(shè)置私有屬性

          function getPrivateProps(obj, filterFunc) {
            return new Proxy(obj, {
              get(obj, prop) {
                if (!filterFunc(prop)) {
                  let value=Reflect.get(obj, prop);
                  // 如果是方法, 將this指向修改原對(duì)象
                  if (typeof value==='function') {
                    value=value.bind(obj);
                  }
                  return value;
                }
              },
              set(obj, prop, value) {
                if (filterFunc(prop)) {
                  throw new TypeError(`Cant set property ${prop}`);
                }
                return Reflect.set(obj, prop, value);
              },
              has(obj, prop) {
                return filterFunc(prop) ? false : Reflect.has(obj, prop);
              },
              ownKeys(obj) {
                return Reflect.ownKeys(obj).filter(prop=> !filterFunc(prop));
              },
              getOwnPropertyDescriptor(obj, prop) {
                return filterFunc(prop) ? undefined : Reflect.getOwnPropertyDescriptor(obj, prop);
              }
            });
          }
          
          function propFilter(prop) {
            return prop.indexOf('_')===0;
          }

          總結(jié)

          本次分享了三種設(shè)計(jì)模式,分別為構(gòu)造函數(shù)模式,外觀模式,代理模式;都是日常開(kāi)發(fā)很常用的設(shè)計(jì)模式。

          其中es6proxy可以訪問(wèn)阮老師博客查看詳細(xì)api,具體使用也可以借鑒vue源碼。代理模式雖然很好用,但也不是任何時(shí)候都建議使用,如果你的代碼需要獲得某些對(duì)象的權(quán)限,不妨可以使用一下代理模式。在比較簡(jiǎn)單的場(chǎng)景,可能就沒(méi)有必要了。

          外觀模式是最常用的,畢竟每一個(gè)js文件總是需要一個(gè)入口的。無(wú)論是main函數(shù)還是init函數(shù),都是起到一個(gè)外觀包裝的作用。當(dāng)然外觀模式并不是必須作為一個(gè)文件入口存在,只要能把重復(fù)的代碼提煉出來(lái),就是一個(gè)合理的外觀模式。

          構(gòu)造函數(shù)模式就不多說(shuō)了,簡(jiǎn)單好用。

          復(fù)制代碼是危險(xiǎn)的。如果有兩段相同的代碼,幾乎可以說(shuō)一定是有問(wèn)題的,因?yàn)槊看胃膭?dòng),要維護(hù)兩段代碼

          盡量減少IO操作,如操作數(shù)據(jù)庫(kù),網(wǎng)絡(luò)發(fā)送,甚至printf ,這些操作比直接操作內(nèi)存,慢很多倍、

          修改Bug時(shí),一定要從最簡(jiǎn)單的基本的地方開(kāi)始檢查,不要檢查到最底層沒(méi)問(wèn)題,發(fā)現(xiàn)是傳入的某個(gè)參數(shù)是錯(cuò)的。先不要懷疑系統(tǒng)的部分。

          設(shè)計(jì)架構(gòu),同時(shí)了解細(xì)節(jié),有些Bug,調(diào)起來(lái)可能費(fèi)時(shí)費(fèi)力,甚至花個(gè)二三天,其實(shí)當(dāng)時(shí)寫的時(shí)候,只要稍微注意,就可以輕松避免。避免Bug的代價(jià)與找出并修改Bug的代價(jià),實(shí)在是差太多了。

          把一段長(zhǎng)代碼,分成很多小函數(shù),便于維護(hù),連自己都不愿看,不愿改的代碼,百分百有問(wèn)題。

          寫程序時(shí),先把流程搞清楚。把各個(gè)流程用的函數(shù)寫清楚,函數(shù)可以留空,這樣編程就變成了填空題。

          做新功能時(shí),把數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì),放在較重要的位置

          希望這次分享能夠既有趣,又讓你得到收獲,謝謝閱讀。

          源自:https://juejin.im/post/5ebe65a0f265da7bd76bb626

          聲明:文章著作權(quán)歸作者所有,如有侵權(quán),請(qǐng)聯(lián)系小編刪除。

          閩南網(wǎng)]

          lol揮別2018起航2019活動(dòng)正式開(kāi)啟,不少玩家想知道召喚師圖標(biāo)免費(fèi)領(lǐng)取方法。

          lol揮別2018起航2019活動(dòng)介紹:

          揮別2018起航2019

          活動(dòng)時(shí)間:即日起-1月14日

          領(lǐng)取截止時(shí)間:2019年1月21日15:59

          活動(dòng)入口:點(diǎn)擊進(jìn)入

          圖標(biāo)領(lǐng)取地址:https://lol.qq.com/act/a20180929awards/index.html#box2

          起航2019召喚師圖標(biāo)寶箱介紹:

          1、一般活動(dòng)結(jié)束后10個(gè)工作日內(nèi)可在本頁(yè)面領(lǐng)取。

          2、請(qǐng)?jiān)诮刂箷r(shí)間前領(lǐng)取道具,逾期作廢。

          3、領(lǐng)取之前請(qǐng)先進(jìn)入一次游戲商城才能收到道具。

          4、領(lǐng)取之后請(qǐng)重新進(jìn)入游戲查收道具。

          關(guān)于lol揮別2018起航2019活動(dòng)的介紹就到這。

          大家好,這是第三篇作者對(duì)于設(shè)計(jì)模式的分享了,前兩篇可以參考:

          手寫一下JavaScript的幾種設(shè)計(jì)模式 (工廠模式,單例模式,適配器模式,裝飾者模式,建造者模式)

          用英雄聯(lián)盟的方式講解JavaScript設(shè)計(jì)模式(一)! (構(gòu)造函數(shù)模式,外觀模式,代理模式)

          設(shè)計(jì)模式在編程開(kāi)發(fā)中用途十分廣泛,每一個(gè)模式描述了一個(gè)在我們周圍不斷重復(fù)發(fā)生的問(wèn)題,以及解決問(wèn)題的核心!很多的時(shí)候,對(duì)于我們其實(shí)如何選擇適合的設(shè)計(jì)模式,才更加消耗時(shí)間。從之前的文章,每一個(gè)設(shè)計(jì)模式都會(huì)有一到兩個(gè)例子,既可以給自己以后開(kāi)發(fā)回憶設(shè)計(jì)模式提供幫助,也希望可以給讀者一些啟發(fā)。

          策略模式


          簡(jiǎn)介

          策略模式定義了算法家族,分別封裝起來(lái),讓他們之間可以互相替換,此模式讓算法的變化不會(huì)影響到使用算法的客戶。

          那聽(tīng)起來(lái)云山霧繞,怎么都涉及到 算法 了 ?難道我一個(gè)前端是時(shí)候進(jìn)攻算法大軍了嗎。其實(shí)并不是,用一個(gè)超級(jí)常見(jiàn)的例子就可以解釋!

          讓我們又回到英雄聯(lián)盟,當(dāng)我們第一次登陸英雄聯(lián)盟的時(shí)候,需要輸入一個(gè)新的姓名吧?起名規(guī)則起碼得有以下這幾條:

          • 名字長(zhǎng)度
          • 名字是否有非法字符
          • 是否重名
          • 不能為空

          其中具體的設(shè)定,只有開(kāi)發(fā)者才知道了,身為玩家只能注意到這幾點(diǎn),那策略模式怎么體現(xiàn)在這里的呢?首先我們實(shí)現(xiàn)一個(gè)顯而易見(jiàn)功能的例子:

          var validator={
              validate: function (value, type) {
                  switch (type) {
                      case 'isNonEmpty ':
                          {
                              return true; // 名字不能為空
                          }
                      case 'isNoNumber ':
                          {
                              return true; // 名字 不是 純數(shù)字
                              break;
                          }
                      case 'isExist ':
                          {
                              return true; // 名字已存在
                          }
                      case 'isLength':
                          {
                              return true; // 長(zhǎng)度合理
                          }
                  }
              }
          };
          復(fù)制代碼

          上述代碼可以實(shí)現(xiàn)一個(gè)表單驗(yàn)證系統(tǒng),剛創(chuàng)建角色起名字的時(shí)候驗(yàn)證那里的功能,只需要傳入相應(yīng)的參數(shù)就可以。

          validator.validate('測(cè)試名字', 'isNumber') // false

          雖然可以得到理想的結(jié)果,但這種寫法有十分嚴(yán)重的缺點(diǎn),最重要的,每次增加或修改規(guī)則時(shí),需要修改整個(gè)validate函數(shù),這不符合開(kāi)放封閉原則,增加邏輯,讓函數(shù)更加復(fù)雜不可控。

          那真正適合的代碼應(yīng)該怎么寫呢?

          var validator={
              // 所有驗(yàn)證規(guī)則處理函數(shù)存放的地方
              types: {},
              validate: function (str, types) {
                  this.messages=[];
                  var checker, result, msg, i;
                  for (i in types) {
                      var type=types[i];
                      checker=this.types[type]; // 獲取驗(yàn)證規(guī)則的驗(yàn)證類
                      if (!checker) { // 如果驗(yàn)證規(guī)則類不存在,拋出異常
                          throw {
                              name: "ValidationError",
                              message: "No handler to validate type " + type
                          };
                      }
          
                      result=checker.validate(str); // 使用查到到的單個(gè)驗(yàn)證類進(jìn)行驗(yàn)證
                      if (!result) {
                          msg="Invalid value for *" + type + "*, " + checker.instructions;
                          this.messages.push(msg);
                      }
                  }
                  
                  return this.hasErrors();
              },
          
              // 是否有message錯(cuò)誤信息
              hasErrors: function () {
                  return this.messages.length !==0;
              }
          };
          復(fù)制代碼

          上面的代碼定義了validator對(duì)象以及validate函數(shù),函數(shù)內(nèi)部會(huì)對(duì)傳入的字符串,檢測(cè)類型數(shù)組進(jìn)行處理。如果存在規(guī)則,進(jìn)行判斷,并把錯(cuò)誤信息發(fā)送到this.message。如果不存在規(guī)則,自然的就不需要繼續(xù)執(zhí)行,拋出error即可。

          // 驗(yàn)證給定的值是否不為空
          validator.types.isNonEmpty={
              validate: function (value) {
                  return value !=="";
              },
              instructions: "傳入的值不能為空"
          };
          
          // 驗(yàn)證給定的值是否 不是 純數(shù)字
          validator.types.isNoNumber={
              validate: function (value) {
                  return isNaN(value); // 偽寫法,因?yàn)閕sNaN會(huì)誤判布爾值和空字符串等,因此并不能作為真正判斷純數(shù)字的依據(jù)
              },
              instructions: "傳入的值不能是純數(shù)字"
          };
          
          // 驗(yàn)證給定的值是否存在
          validator.types.isExist={
              validate: function (value) {
                  // $.ajax() ...
                  return true;
              },
              instructions: "給定的值已經(jīng)存在"
          };
          
          // 驗(yàn)證給定的值長(zhǎng)度是否合理
          validator.types.isLength={
              validate: function (value) {
                  var l=value.toString().length
                  if ( l > 2 && l < 10) {
                      return true;
                  } else {
                      return false;
                  }
              },
              instructions: "長(zhǎng)度不合理,請(qǐng)長(zhǎng)度在2-10個(gè)字符內(nèi)"
          };
          復(fù)制代碼

          上面對(duì)types規(guī)則進(jìn)行了補(bǔ)充,定義了幾種規(guī)則,至此,對(duì)于名稱校驗(yàn),簡(jiǎn)單的設(shè)定就敲完了。接下來(lái)要準(zhǔn)備的就是一個(gè)能夠在英雄聯(lián)盟合理的名字進(jìn)行驗(yàn)證:

          var types=['isExist', 'isLength', 'isNoNumber', 'isNonEmpty']; // 決定想要的規(guī)則,無(wú)論增加或者減少,原函數(shù)都不需要改動(dòng)
          function check (name, types) {
              validator.validate(name, types);
              if (validator.hasErrors()) {
                  console.log(validator.messages.join("\n"));
              } else {
                  console.log('驗(yàn)證通過(guò)!')
              }
          }
          check('okckokckokck', types) // 長(zhǎng)度不合理,請(qǐng)長(zhǎng)度在2-10個(gè)字符內(nèi)
          check('老faker', types) // true
          check('00001', types) // 傳入的值不能是純數(shù)字
          復(fù)制代碼

          首先設(shè)定好想要的規(guī)則,用一個(gè)types數(shù)組囊括進(jìn)來(lái),之后定義一個(gè)check函數(shù),把結(jié)果處理封裝一下,最后傳入?yún)?shù),無(wú)論想要檢測(cè)什么規(guī)則,都不需要修改原函數(shù)?,F(xiàn)在無(wú)論我想檢測(cè)faker可不可以注冊(cè),還是一個(gè)空字符串,都可以傳入規(guī)則,進(jìn)行使用。如果想添加新的規(guī)則,只需要在validator.types上續(xù)寫對(duì)象就可以,方便清晰,結(jié)構(gòu)明朗。

          核心思想就是把復(fù)雜的算法結(jié)構(gòu),分別封裝起來(lái),讓他們之間可以互相替換,上面的代碼就很好的體現(xiàn)了 互相替換 ,因?yàn)闊o(wú)論我怎么去修改想要的規(guī)則,都不需要改動(dòng)原本的代碼。

          橋接模式

          簡(jiǎn)介

          在系統(tǒng)沿著多個(gè)維度變化的同時(shí),又不增加其復(fù)雜度并已達(dá)到解耦。將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。簡(jiǎn)單的說(shuō):橋接模式最主要的特點(diǎn)是實(shí)現(xiàn)層(如元素綁定的事件)與抽象層(如修飾頁(yè)面UI邏輯)解耦分離。

          下面依然是一個(gè)例子:

          假如我們還在英雄聯(lián)盟的世界里,每一場(chǎng)游戲最終都會(huì)有一個(gè)結(jié)局,無(wú)論勝利還是失敗,都會(huì)彈出一個(gè)窗口,告訴你 —— Victory或者是Defeat。

          function GameMessage (type) { // 抽象 與 實(shí)現(xiàn) 的 橋梁
              this.fn=type ? new Victory() : new Defeat()
          }
          GameMessage.prototype.show=function() {
              this.fn.show()
          }
          
          function Defeat() { // 抽象層
              this.show=function() {
                  console.log('im loser')
              }
          }
          
          function Victory() { // 抽象層
              this.show=function() {
                  console.log('im winner')
              }
          }
          
          // 實(shí)現(xiàn)層
          function getResult() {
              var switchVD=Math.ceil(Math.random()*10) > 5 // 勝利失敗一半一半
              return new GameMessage(switchVD)
          }
          var result1=getResult()
          var result2=getResult()
          var result3=getResult()
          result1.show()
          result2.show()
          result3.show()
          
          復(fù)制代碼

          首先我們創(chuàng)建了一個(gè)GameMessage的函數(shù),我們都知道勝利失敗都有一半的概率,因此定義了switchVD變量,模擬一個(gè)隨機(jī)事件,同時(shí)每次結(jié)果調(diào)用一次getResult函數(shù),獲取最新結(jié)果。

          橋接模式體現(xiàn)在GameMessage函數(shù)上,將抽象的 Victory() 以及 Defeat() 與 我們獲取結(jié)果的 getResult() 實(shí)現(xiàn)解耦。函數(shù)之間不糅合邏輯,但又通過(guò)橋梁函數(shù),連接在一起。

          這么寫的好處就是,兩者都可以獨(dú)立的變化,互不打擾。畢竟如果揉在一起,可能邏輯如下:

          function Defeat() { // 抽象層
              this.show=function() {
                  console.log('im loser')
              }
          }
          
          function Victory() { // 抽象層
              this.show=function() {
                  console.log('im winner')
              }
          }
          
          var switchVD=Math.ceil(Math.random()*10) > 5
          if (switchVD) {
              var result=new Victory()
          } else {
              var result=new Defeat()
          }
          
          result.show() // loser or winner
          復(fù)制代碼

          上述代碼可以輕松的看到,如果沒(méi)有橋接模式,直接把實(shí)現(xiàn)層,渲染層糅合在一起,會(huì)依賴上下文。倘若獲取不到上下文的環(huán)境,很容易出現(xiàn)問(wèn)題。

          小結(jié)

          橋接模式在日常開(kāi)發(fā)中,會(huì)在不經(jīng)意間頻繁使用,目的也是為了讓代碼結(jié)構(gòu)清晰,將不同邏輯的代碼互相解耦。便于日后維護(hù),開(kāi)發(fā)時(shí)也更能區(qū)分模塊,看的舒服,自然效率也高。

          橋接模式關(guān)鍵是要理解抽象部分與實(shí)現(xiàn)部分的分離,使得二者可以獨(dú)立的變化,而不必拘泥于形式。靈活的變化,適用場(chǎng)景的多變就非常適合使用這種模式來(lái)實(shí)現(xiàn)。橋接模式最重要的是找到代碼中不同的變化緯度。

          狀態(tài)模式

          簡(jiǎn)介

          狀態(tài)模式(State)允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變的時(shí)候改變它的行為,對(duì)象看起來(lái)似乎修改了它的類。 其實(shí)就是用一個(gè)對(duì)象或者數(shù)組記錄一組狀態(tài),每個(gè)狀態(tài)對(duì)應(yīng)一個(gè)實(shí)現(xiàn),實(shí)現(xiàn)的時(shí)候根據(jù)狀態(tài)挨個(gè)去運(yùn)行實(shí)現(xiàn)。

          優(yōu)點(diǎn):

          1. 一個(gè)狀態(tài)對(duì)應(yīng)一個(gè)行為,直觀清晰,增改方便。
          2. 狀態(tài)與狀態(tài)間,行為與行為間彼此獨(dú)立互不干擾。
          3. 避免對(duì)象條件判斷語(yǔ)句過(guò)多。
          4. 不用執(zhí)行不必要的判斷語(yǔ)句。

          缺點(diǎn):

          1. 需要將事物的不同狀態(tài)以及對(duì)應(yīng)的行為拆分出來(lái),有時(shí)候會(huì)過(guò)度設(shè)計(jì)。
          2. 必然會(huì)增加事物類和動(dòng)作類的個(gè)數(shù),動(dòng)作類再根據(jù)單一原則,分別拆成幾個(gè)類,會(huì)反而使得代碼混亂。

          比如下面我們定義一個(gè)英雄的狀態(tài),名字叫亞索,其中亞索可能同時(shí)有好幾個(gè)狀態(tài)比如 邊走邊攻擊 —— 我們俗稱的“走A”,還有可能釋放技能之后接一個(gè)“B鍵回家”的操作,當(dāng)然最有可能的是eqw閃r行云流水的操作收獲一個(gè)人頭,再接一個(gè)ctrl+f6等。


          如果對(duì)這些操作一個(gè)個(gè)進(jìn)行處理判斷,需要多個(gè)if-else或switch不僅丑陋不說(shuō),而且在遇到有組合動(dòng)作的時(shí)候,實(shí)現(xiàn)就會(huì)更為冗余。那么我們這里的復(fù)雜操作,可以使用 狀態(tài)模式 來(lái)實(shí)現(xiàn)。

          狀態(tài)模式 的思路是:首先創(chuàng)建一個(gè)狀態(tài)對(duì)象或者數(shù)組,在對(duì)象內(nèi)部存儲(chǔ)需要操作的狀態(tài)數(shù)組或?qū)ο螅缓鬆顟B(tài)對(duì)象提供一些接口,可以更改狀態(tài)以及執(zhí)行動(dòng)作。

          那現(xiàn)在有一個(gè)英雄叫做亞索!下面代碼,我們就用亞索的狀態(tài)來(lái)實(shí)現(xiàn)一下傳說(shuō)中的狀態(tài)模式:

          function YasuoState() {
              //存儲(chǔ)當(dāng)前即將執(zhí)行動(dòng)作的狀態(tài)!
              this.currentstate=[];
          
              this.Actions={
                  walk : function(){
                      console.log('walk');
                  },
                  attack : function(){
                      console.log('attack');
                  },
                  magic : function(){
                      console.log('magic');
                  },
                  backhome : function(){
                      console.log('backhome');
                  }
              };
          }
            
          YasuoState.prototype.changeState=function() {
              //清空當(dāng)前的動(dòng)作
              this.currentstate=[];
              Object.keys(arguments).forEach((i)=> this.currentstate.push(arguments[i]))
              return this;
          }
          YasuoState.prototype.YasuoActions=function() {
              //當(dāng)前動(dòng)作集合中的動(dòng)作依次執(zhí)行
              this.currentstate.forEach((k)=> this.Actions[k] && this.Actions[k]())
              return this;
          }
          
          
            
          var yasuoState=new YasuoState();
            
          yasuoState.changeState('walk','attack').YasuoActions().changeState('walk').YasuoActions().YasuoActions();
          復(fù)制代碼

          上面代碼成功實(shí)現(xiàn)了亞索的狀態(tài)模式,我們假設(shè)他有走路、攻擊、釋放技能、回家?guī)讉€(gè)狀態(tài),其中這幾個(gè)狀態(tài)其實(shí)是可以同時(shí)輸入指令的,要不然那些職業(yè)選手的高光操作就會(huì)在 技能銜接 而出現(xiàn)的卡頓 香消玉殞。

          狀態(tài)模式最常見(jiàn)的就是日常的例子 —— 紅綠燈,每當(dāng)切換狀態(tài)的時(shí)候,執(zhí)行一次動(dòng)作。

          至于英雄聯(lián)盟中,最常見(jiàn)的就是邊走邊攻擊,在輸入命令后,首先改變了我們對(duì)象的狀態(tài)yasuoState.changeState('magic','backhome'),然后因?yàn)樵诖a中有return this;,可以鏈?zhǔn)秸{(diào)用接下來(lái)的行為,于是我們讓它依次執(zhí)行剛才輸入的狀態(tài)。接下來(lái)又一次改變了狀態(tài)changeState('walk'),并且進(jìn)行執(zhí)行。可以看到執(zhí)行了兩次,由于狀態(tài)并沒(méi)有再次改變,因此只需要重復(fù)執(zhí)行就可以保證我們的英雄一直往前走下去了。

          希望狀態(tài)模式可以幫助你解決絕大多數(shù),需要切換狀態(tài)的操作。遇到類似的問(wèn)題時(shí),可以迅速拿出成熟可靠的狀態(tài)模式解決之。

          總結(jié)

          本次分享的三種模式,都可以在英雄聯(lián)盟中找到影子,因?yàn)槲蚁矚g這款游戲,所以很輕松可以找到其中使用的設(shè)計(jì)模式:

          • 策略模式 —— 行為模式,當(dāng)業(yè)務(wù)需要很多種判斷,甚至組合計(jì)算時(shí),為了方便擴(kuò)展修改或使用它。
          • 橋接模式 —— 結(jié)構(gòu)型模式,構(gòu)建需要經(jīng)常擴(kuò)展功能的對(duì)象時(shí),經(jīng)常會(huì)遇到。
          • 狀態(tài)模式 —— 行為模式,希望業(yè)務(wù)根據(jù)狀態(tài)改變而發(fā)生改變,最常見(jiàn)的現(xiàn)實(shí)例子就是紅綠燈。

          設(shè)計(jì)模式主要可以幫助我們解決,開(kāi)發(fā)中對(duì)代碼的設(shè)計(jì)問(wèn)題,那我們?nèi)绾握业胶线m的對(duì)象,并應(yīng)用合適的設(shè)計(jì)模式呢?

          借用書中的幾個(gè)提示吧:

          尋找合適的對(duì)象

          決定對(duì)象的粒度

          決定好這個(gè)對(duì)象設(shè)計(jì)的接口

          把對(duì)象需要的具體函數(shù)實(shí)現(xiàn)

          合理的運(yùn)用代碼復(fù)用機(jī)制

          設(shè)計(jì)的代碼應(yīng)該可以支持變化,要對(duì)變化有預(yù)見(jiàn)性

          大概是這幾種,在 javascript 中涉及編譯的場(chǎng)景較少,就不敘述了。

          設(shè)計(jì)模式是軟件開(kāi)發(fā)人員在軟件開(kāi)發(fā)過(guò)程中面臨的一般問(wèn)題的解決方案。這些解決方案是眾多軟件開(kāi)發(fā)人員經(jīng)過(guò)相當(dāng)長(zhǎng)的一段時(shí)間的試驗(yàn)和錯(cuò)誤總結(jié)出來(lái)的。

          根據(jù)上面的幾條規(guī)則,在開(kāi)發(fā)接口和函數(shù)的時(shí)候,時(shí)刻注意,就可以避免大多數(shù)代碼設(shè)計(jì)上的問(wèn)題,對(duì)于以后的維護(hù)也會(huì)有巨大的幫助。下一個(gè)接受代碼的人,也會(huì)十分感激你的,讀代碼其實(shí)和讀書一樣,你現(xiàn)在偷懶寫的代碼可能無(wú)所謂,后面接手的人會(huì)瘋狂吐槽。相反如果你優(yōu)雅的實(shí)現(xiàn),像我,就會(huì)心里由衷的佩服,看到整齊的函數(shù),注釋明朗的功能,不得不說(shuō),高手確實(shí)是高手啊,短短 200 行,讓人跪服,就突出一個(gè)詞 —— 優(yōu)雅。



          相關(guān)參考

          • tom大叔博客
          • 《設(shè)計(jì)可復(fù)用的設(shè)計(jì)模式》

          作者:黃梵高
          鏈接:https://juejin.im/post/5ec791fdf265da770d3daabd


          主站蜘蛛池模板: 成人精品一区二区不卡视频| 国产精品无码不卡一区二区三区| 亚洲AV日韩AV一区二区三曲| 亚洲第一区视频在线观看 | 精品一区二区三区波多野结衣 | 蜜臀AV在线播放一区二区三区| 国产成人精品无码一区二区三区 | 亚洲日韩精品国产一区二区三区 | 一区二区亚洲精品精华液| 亚洲av成人一区二区三区观看在线 | 国内精品无码一区二区三区| 亚洲一区二区三区久久| 国产精品福利一区| 亚洲一区精品中文字幕| 亚洲国产成人久久综合一区77| 性无码免费一区二区三区在线 | 蜜芽亚洲av无码一区二区三区 | 国产成人av一区二区三区不卡| 亚洲国产专区一区| 无码av免费毛片一区二区| 人妻少妇精品一区二区三区| 无码国产精品久久一区免费| 人妖在线精品一区二区三区| 亚州国产AV一区二区三区伊在| 一区二区亚洲精品精华液| 一区二区三区国产精品| 无码精品国产一区二区三区免费| 日韩精品一区二区三区老鸦窝| 亚洲一区中文字幕| 蜜臀AV免费一区二区三区| 亚洲电影一区二区| 日韩精品无码免费一区二区三区| 中文字幕一区二区人妻性色| 人妻互换精品一区二区| 国产激情一区二区三区在线观看 | 精品一区二区三区在线视频| 精品一区狼人国产在线| 日韩最新视频一区二区三| 国产成人精品一区二区三区无码| 51视频国产精品一区二区| 一区二区乱子伦在线播放|