<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          收藏給你的女朋友,這10 種常用設(shè)計模式

          共 16893字,需瀏覽 34分鐘

           ·

          2021-05-10 21:42

          用自己通俗易懂的語言理解設(shè)計模式。

          通過對每種設(shè)計模式的學習,更加加深了我對它的理解,也能在工作中考慮應(yīng)用場合。

          成文思路:分析每種設(shè)計模式思想、抽離出應(yīng)用場景、對這些模式進行對比

          此篇文章包含:修飾者模式(裝飾器)、單例模式、工廠模式、訂閱者模式、觀察者模式、代理模式

          將不變的部分和變化的部分隔開是每個設(shè)計模式的主題。

          單例模式

          也叫單體模式,核心思想是確保一個類只對應(yīng)一個實例。

          特點:

          • 只允許一個例存在,提供全局訪問點,緩存初次創(chuàng)建的變量對象

          • 排除全局變量,防止全局變量被重寫

          • 可全局訪問

            // 工廠模式和new模式實現(xiàn)單例模式

            vue的安裝插件屬于單例模式

            適用場景:適用于彈框的實現(xiàn), 全局緩存,一個單一對象。比如:彈窗,無論點擊多少次,彈窗只應(yīng)該被創(chuàng)建一次。

            缺點:

          防止全局變量被污染,看過多種寫法,總結(jié)在一起,更能融會貫通

          直接使用字面量(全局對象)

          const person = {
            name'哈哈',
            age18
          }

          了解 const 語法的小伙伴都知道,這只喵是不能被重新賦值的,但是它里面的屬性其實是可變的。

          如果想要一個不可變的單例對象:

          const person = {
            name'哈哈',
            age18
          }
          Object.freeze(person);

          這樣就不能新增或修改person的任何屬性.

          如果是在模塊中使用,上面的寫法并不會污染全局作用域,但是直接生成一個固定的對象缺少了一些靈活性。

          使用構(gòu)造函數(shù)的靜態(tài)屬性

          class寫法

          class A {
            constructor () {
              if (!A._singleton) {
                A._singleton = this;
              }
              return A._singleton;
            }
            log (...args) {
              console.log(...args);
            }
          }
          var a1 = new A() 
          var a2= new A()
          console.log(a1 === a2)//true

          構(gòu)造函數(shù)寫法

          function A(name){
              // 如果已存在對應(yīng)的實例
             if(typeof A._singleton === 'object'){
                 return A._singleton
             }
             //否則正常創(chuàng)建實例
             this.name = name
             
             // 緩存
             A._singleton =this
             return this
          }
          var a1 = new A() 
          var a2= new A()
          console.log(a1 === a2)//true

          缺點:在于靜態(tài)屬性是能夠被人為重寫的,不過不會像全局變量那樣被無意修改。

          借助閉包

          1. 考慮重寫構(gòu)造函數(shù):當對象第一次被創(chuàng)建以后,重寫構(gòu)造函數(shù),在重寫后的構(gòu)造函數(shù)里面訪問私有變量。
          function A(name){
            var instance = this
            this.name = name
            //重寫構(gòu)造函數(shù)
            A = function (){
                return instance
            }
              //重寫構(gòu)造函數(shù)之后,實際上原先的A指針對應(yīng)的函數(shù)實際上還在內(nèi)存中(因為instance變量還在被引用著),但是此時A指針已經(jīng)指向了一個新的函數(shù)了
          }
          A.prototype.pro1 = "from protptype1"

          var a1 = new A() 
          A.prototype.pro2 = "from protptype2"
          var a2= new A()

          console.log(a1.pro1)//from protptype1
          console.log(a1.pro2)//underfined
          console.log(a2.pro1)//from protptype1
          console.log(a2.pro2)//underfined
          console.log(a1.constructor ==== A)  //false

          為了解決A指針指向新地址的問題,實現(xiàn)原型鏈繼承

          function A(name){
            var instance = this
            this.name = name
           
            //重寫構(gòu)造函數(shù)
            A = function (){
                return instance
            }
            
            // 第一種寫法,這里實際上實現(xiàn)了一次原型鏈繼承,如果不想這樣實現(xiàn),也可以直接指向舊的原型
            A.prototype = this
            // 第二種寫法,直接指向舊的原型
            A.prototype = this.constructor.prototype
            
            instance = new A()
            
            // 調(diào)整構(gòu)造函數(shù)指針,這里實際上實現(xiàn)了一次原型鏈繼承,如果不想這樣實現(xiàn),也可以直接指向原來的原型
            instance.constructor = A
           
            return instance
          }
          A.prototype.pro1 = "from protptype1"

          var a1 = new A() 
          A.prototype.pro2 = "from protptype2"
          var a2= new A()

          console.log(a1.pro1)//from protptype1
          console.log(a1.pro2)//from protptype2
          console.log(a2.pro1)//from protptype1
          console.log(a2.pro2)//from protptype2

          1. 利用立即執(zhí)行函數(shù)來保持私有變量
          var A;
          (function(name){
              var instance;
              A = function(name){
                  if(instance){
                      return instance
                  }
                  
                  //賦值給私有變量
                  instance = this
                  
                  //自身屬性
                  this.name = name
              }
          }());
          A.prototype.pro1 = "from protptype1"

          var a1 = new A('a1'
          A.prototype.pro2 = "from protptype2"
          var a2 = new A('a2')

          console.log(a1.name)
          console.log(a1.pro1)//from protptype1
          console.log(a1.pro2)//from protptype2
          console.log(a2.pro1)//from protptype1
          console.log(a2.pro2)//from protptype2

          以上通過閉包的方式可以實現(xiàn)單例

          代理實現(xiàn)單例模式

           function singleton(name){
              this.name = name
            }
            let proxySingleton = function(){
              let instance = null
              return function(name){
                if(!instance){
                  instance = new singleton(name)
                }
                return instance
              }
            }()
            let a1= new proxySingleton('a1')
            let a2= new proxySingleton('a2')

           console.log(123, a1===a2)

          工廠單例

          let logger = null
          class Logger {
            log (...args) {
              console.log(...args);
            }
          }
          function createLogger({
            if (!logger) {
              logger = new Logger();
            }
            return logger;
          }
          let a = new createLogger().log('12')
          let b = new createLogger().log('121')
          console.log(new createLogger(), a===b)

          根據(jù)理解,我自己喜歡用代理方式實現(xiàn),更好理解。如果總結(jié)有錯,歡迎指正。

          參考:單例模式

          工廠模式

          不暴露創(chuàng)建對象的邏輯,封裝在一個函數(shù)中。工廠模式根據(jù)抽象程度的不同可以分為:簡單工廠,工廠方法和抽象工廠。

          簡單工廠模式

          簡單工廠模式又叫靜態(tài)工廠模式,由一個工廠對象決定創(chuàng)建某一種產(chǎn)品對象類的實例。主要用來創(chuàng)建同一類對象。

          簡單工廠的優(yōu)點在于,你只需要一個正確的參數(shù),就可以獲取到你所需要的對象,而無需知道其創(chuàng)建的具體細節(jié)。

          但是在函數(shù)內(nèi)包含了所有對象的創(chuàng)建邏輯(構(gòu)造函數(shù))和判斷邏輯的代碼,每增加新的構(gòu)造函數(shù)還需要修改判斷邏輯代碼。當我們的對象不是上面的3個而是30個或更多時,這個函數(shù)會成為一個龐大的超級函數(shù),便得難以維護。所以,簡單工廠只能作用于創(chuàng)建的對象數(shù)量較少,對象的創(chuàng)建邏輯不復雜時使用。

          工廠方法模式

          工廠方法模式的本意是將實際創(chuàng)建對象的工作推遲到子類中,這樣核心類就變成了抽象類。

          在工廠方法模式中,工廠父類負責定義創(chuàng)建產(chǎn)品對象的公共接口,而工廠子類則負責生成具體的產(chǎn)品對象, 這樣做的目的是將產(chǎn)品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應(yīng)該實例化哪一個具體產(chǎn)品類。

          抽象工廠模式

          抽象工廠其實是實現(xiàn)子類繼承父類的方法。

          抽象工廠模式(Abstract Factory Pattern),提供一個創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而無須指定它們具體的類。

          抽象工廠可以提供多個產(chǎn)品對象,而不是單一的產(chǎn)品對象。

          參考 :JavaScript設(shè)計模式與實踐--工廠模式

          觀察者模式或發(fā)布訂閱模式

          通常又被稱為 發(fā)布-訂閱者模式 或 消息機制,它**定義了對象間的一種一對多的依賴關(guān)系**,只要當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新,解決了主體對象與觀察者之間功能的耦合,即一個對象狀態(tài)改變給其他對象通知的問題。

          最好理解的舉例:公司里發(fā)布通知,讓員工都知道。

          工作中碰到以下幾種,并進行分析。

          用雙向綁定來分析此模式:

          雙向綁定維護4個模塊:observer監(jiān)聽者、dep訂閱器、watcher訂閱者、compile編譯者

          訂閱器是手機訂閱者(依賴),如果屬性發(fā)生變化observer通知dep,dep通知watcher調(diào)用update函數(shù)(watcher類中有update函數(shù),并且將自己加入dep)去更新數(shù)據(jù),這是符合一對多的思想,也就是observer是一,watcher是多。compile解析指令,訂閱數(shù)據(jù)變化,綁定更新函數(shù)。

          理解下來,compile類似于綁定員工的角色,把watcher加入一個集體,observer通知它們執(zhí)行。

          用子組件與父組件通信分析此模式:

          通過 emit來發(fā)布消息,對訂閱者emit 來發(fā)布消息,對訂閱者 emit來發(fā)布消息,對訂閱者on 做統(tǒng)一處理。

          emit是發(fā)布訂閱者,emit是發(fā)布訂閱者,emit是發(fā)布訂閱者,on 是監(jiān)聽執(zhí)行

          用DOM的事件綁定(比如click)分析此模式:

          addEventListener('click',()=>{})監(jiān)聽click事件,當點擊DOM就是向訂閱者發(fā)布這個消息。

          點擊DOM是發(fā)布,addEventListener是監(jiān)聽執(zhí)行

          小結(jié)

          通過分析平時碰到的這種模式,更好的理解一和多分別對應(yīng)什么,也增加記憶。

          一(發(fā)布事件):通知、廣播發(fā)布

          多(訂閱事件,可能會做出不同的回應(yīng)):觀察者、監(jiān)聽者、訂閱者

          發(fā)布和訂閱的意思都是變成多的角度(添加)

          一是對應(yīng)執(zhí)行,多是收集

          平時碰到的函數(shù)理解,寫自己的函數(shù)也可以這么定義,有個全局Events=[]:

          publish/emit/點擊/notify 訂閱事件 調(diào)用函數(shù) subscribe/on/addEventListener 監(jiān)聽 push函數(shù)
          unsubscribe/off/removeEventListener 刪除

          其實分析以上幾種情況,發(fā)布訂閱模式和觀察者模式的思想差不多相同,但是也是有區(qū)別:

          • 觀察者模式中需要觀察者對象自己定義事件發(fā)生時的相應(yīng)方法。
          • 發(fā)布訂閱模式者在發(fā)布對象和訂閱對象之中加了一個中介對象。我們不需要在乎發(fā)布者對象和訂閱者對象的內(nèi)部是什么,具體響應(yīng)時間細節(jié)全部由中介對象實現(xiàn)。
          訂閱的東西用Map或者Object類型來存儲。

          發(fā)布訂閱模式,有個中介,也可以說是channel,但發(fā)現(xiàn)代碼實現(xiàn)差不多,只不過發(fā)布訂閱用來寫包含有回調(diào)函數(shù)

          實例參考:JavaScript 設(shè)計模式之觀察者模式與發(fā)布訂閱模式

          juejin.cn/post/684490…

          juejin.cn/post/684490…

          裝飾者模式

          裝飾者模式的定義:在不改變對象自身的基礎(chǔ)上,在程序運行期間給對象動態(tài)地添加方法。

          裝飾者模式的適用場合:

          • 如果你需要為類增添特性或職責,可是從類派生子類的解決方法并不太現(xiàn)實的情況下,就應(yīng)該使用裝飾者模式。
          • 如果想為對象增添特性又不想改變使用該對象的代碼的話,則可以采用裝飾者模式。
          • 原有方法維持不變,在原有方法上再掛載其他方法來滿足現(xiàn)有需求;函數(shù)的解耦,將函數(shù)拆分成多個可復用的函數(shù),再將拆分出來的函數(shù)掛載到某個函數(shù)上,實現(xiàn)相同的效果但增強了復用性。

          裝飾者模式除了可以應(yīng)用在類上之外,還可以應(yīng)用在函數(shù)上(其實這就是高階函數(shù))

          我覺得可以是函數(shù)封裝原函數(shù)。這樣不改變原來

          舉例:為汽車添加反光燈、后視鏡等這些配件

          碰到的:對函數(shù)進行增強(節(jié)流函數(shù)or防抖函數(shù)、緩存函數(shù)返回值、構(gòu)造React高階組件,為組件增加額外的功能)

          參考: 使用裝飾者模式做有趣的事情

          代理模式

          所謂的的代理模式就是為一個對象找一個替代對象,以便對原對象進行訪問。

          使用代理的原因是我們不愿意或者不想對原對象進行直接操作,我們使用代理就是讓它幫原對象進行一系列的操作,等這些東西做完后告訴原對象就行了。就像我們生活的那些明星的助理經(jīng)紀人一樣。

          原則:單一原則

          常用的虛代理形式:保護代理、緩存代理、虛擬代理。

          保護代理:明星委托助理或者經(jīng)紀人所要干的事;

          緩存代理:緩存代理就是將代理加緩存,更方便單一原則;

          常用的虛擬代理:某一個花銷很大的操作,可以通過虛擬代理的方式延遲到這種需要它的時候才去創(chuàng)建(例:使用虛擬代理實現(xiàn)圖片懶加載);

          先占位,加載完,再加載所需圖片
          var imgFunc = (function({
              var imgNode = document.createElement('img');
              document.body.appendChild(imgNode);
              return {
                  setSrcfunction(src{
                      imgNode.src = src;
                  }
              }
          })();
          var proxyImage = (function({
              var img = new Image();
              img.onload = function({
                  imgFunc.setSrc(this.src);
              }
              return {
                  setSrcfunction(src{
                      imgFunc.setSrc('./loading,gif');
                      img.src = src;
                  }
              }
          })();
          proxyImage.setSrc('./pic.png');

          碰到的:Vue的Proxy、懶加載圖片加占位符、冒泡點擊DOM元素

          參考:javascript 代理模式(通俗易懂)

          策略模式

          策略模式的定義:定義一系列的算法,把他們一個個封裝起來,并且使他們可以相互替換

          策略模式的重心不是如何實現(xiàn)算法,而是如何組織、調(diào)用這些算法,從而讓程序結(jié)構(gòu)更靈活、可維護、可擴展。

          策略模式的目的:將算法的使用算法的實現(xiàn)分離開來。

          一個基于策略模式的程序至少由兩部分組成:

          • 第一個部分是一組策略類(可變),策略類封裝了具體的算法,并負責具體的計算過程。
          • 第二個部分是環(huán)境類Context(不變),Context接受客戶的請求,隨后將請求委托給某一個策略類。要做到這一點,說明Context中要維持對某個策略對象的引用。

          原則:開放-封閉原則

          /*策略類 A B C就是可以替換使用的算法*/
          var levelOBJ = {
              "A"function(money{
                  return money * 4;
              },
              "B" : function(money{
                  return money * 3;
              },
              "C" : function(money{
                  return money * 2;
              } 
          };
          /*環(huán)境類,維持對levelOBJ策略對象的引用,擁有執(zhí)行算法的能力*/
          var calculateBouns =function(level,money{
              return levelOBJ[level](money);
          };

          console.log(calculateBouns('A',10000)); // 40000

          Context函數(shù)傳入實際值,調(diào)用策略,可能同時調(diào)用多個策略,這樣可以封裝一函數(shù)循環(huán)調(diào)用策略,然后用Context函數(shù)調(diào)用此封裝的函數(shù)

          在工作中,很多if else,每種條件執(zhí)行不同的算法,其實可以用到策略模式,比如驗證表單

          比如: 
          多種不同登錄方式(賬號密碼登錄、手機驗證碼登錄和第三方登錄)。為了方便維護不同的登錄方式,可以把不同的登錄方式封裝成不同的登錄策略。

          驗證表單

          不同的人發(fā)不同的工資

          工作中碰到選擇不同下拉框執(zhí)行不同函數(shù)(策略)

          參考:js設(shè)計模式--策略模式

          建造者模式

          應(yīng)用場景:

          1. 創(chuàng)建時有很多必填參數(shù)需要驗證。
          2. 創(chuàng)建時參數(shù)求值有先后順序、相互依賴。
          3. 創(chuàng)建有很多步驟,全部成功才能創(chuàng)建對象。
          class Programmer {
            age: number
            username: string
            color: string
            area: string

            constructor(p) {
              this.age = p.age
              this.username = p.username
              this.color = p.color
              this.area = p.area
            }

            toString() {
              console.log(this)
            }
          }

          class Builder {
            age: number
            username: string
            color: string
            area: string

            build() {
              if (this.age && this.username && this.color && this.area) {
                return new Programmer(this)
              } else {
                throw new Error('缺少信息')
              }
            }

            setAge(age: number) {
              if (age > 18 && age < 36) {
                this.age = age
                return this
              } else {
                throw new Error('年齡不合適')
              }
            }

            setUsername(username: string) {
              if (username !== '小明') {
                this.username = username
                return this
              } else {
                throw new Error('小明不合適')
              }
            }

            setColor(color: string) {
              if (color !== 'yellow') {
                this.color = color
                return this
              } else {
                throw new Error('yellow不合適')
              }
            }

            setArea(area: string) {
              this.area = area
              return this
            }
          }

          // test
          const p = new Builder()
            .setAge(20)
            .setUsername('小紅')
            .setColor('red')
            .setArea('hz')
            .build()
            .toString()


          適配模式

          舉例:Target Adaptee Adapter ,Adapter是需要繼承Target,并在里面調(diào)用Adaptee中的方法。

          形象比擬:Target是目標抽象類,實現(xiàn)插入插口的功能;Adaptee是新的插頭,包含了實現(xiàn)目標的方法;Adapter是implements Target,為了調(diào)用Target方法。這樣,既能保留原功能(原函數(shù)不變),又能執(zhí)行新功能(添加Adaptee Adapter)

          Target是要實現(xiàn)的目標(比如打印日志,這是抽象的方法),如果不用適配模式,就需要重寫函數(shù),找到辦法。

          Adaptee是適配者類,也就是插口,在適配器Adapter中,implements來自于的Target方法(就是新方法,適配的方法,達到)中調(diào)用Adaptee中的方法。

          Adaptee在Adapter中調(diào)用,Adapter最終是要調(diào)用Adaptee 中需要的具體方法(也就是我們最終要達到目的使用的方法,此方法可在Target中的抽象方法中實現(xiàn))。

          場景:
          1.以前開發(fā)的接口不滿足需求,比如輸出log存在本地盤改成存入云盤
          2.使用第三方提供的組件,但組件接口定義和自己要求的接口定義不同

          面試過程中,定義Target Adapter Adaptee分別實現(xiàn)的功能,再套用原理來實現(xiàn)。

          用ts實現(xiàn),這樣能使用interface和implements,更符合面向?qū)ο笳Z言

          優(yōu)點

          • 將目標類和適配者類解耦,通過引入一個適配器類來重用現(xiàn)有的適配者類,而無須修改原有代碼。

          • 增加了類的透明性和復用性,將具體的實現(xiàn)封裝在適配者類中,對于客戶端類來說是透明的,而且提高了適配者的復用性。

          • 靈活性和擴展性都非常好,通過使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎(chǔ)上增加新的適配器類,符合開閉原則

          缺點

          • 過多地使用適配器,會讓系統(tǒng)非常零亂,不易整體進行把握。

          參考:TypeScript 設(shè)計模式之適配器模式

          模板方法模式

          很好理解,看它能很快理解

          這九種常用的設(shè)計模式你掌握了嗎

          職責鏈模式

          應(yīng)用場景:

          1. 多個處理器 ABC 依次處理同一個請求,形成一個鏈條,當某個處理器能處理這個請求,就不會繼續(xù)傳遞給后續(xù)處理器了。
          2. 過濾器 攔截器 處理器。
          const order500 = function (orderType, pay, stock{
            if (orderType === 1 && pay === true) {
              console.log("500 元定金預購, 得到 100 元優(yōu)惠券");
              return true;
            } else {
              return false;
            }
          };

          const order200 = function (orderType, pay, stock{
            if (orderType === 2 && pay === true) {
              console.log("200 元定金預購, 得到 50 元優(yōu)惠券");
              return true;
            } else {
              return false;
            }
          };

          const orderCommon = function (orderType, pay, stock{
            if ((orderType === 3 || !pay) && stock > 0) {
              console.log("普通購買, 無優(yōu)惠券");
              return true;
            } else {
              console.log("庫存不夠, 無法購買");
              return false;
            }
          };

          class chain {
            fnFunction
            nextFnFunction

            constructor(fn: Function) {
              this.fn = fn;
              this.nextFn = null;
            }

            setNext(nextFn) {
              this.nextFn = nextFn
            }

            init(...arguments) {
              const result = this.fn(...arguments);
              if (!result && this.nextFn) {
                this.nextFn.init(...arguments); //這里看不懂
              }
            }
          }

          const order500New = new chain(order500);
          const order200New = new chain(order200);
          const orderCommonNew = new chain(orderCommon);

          order500New.setNext(order200New);
          order200New.setNext(orderCommonNew);

          order500New.init(3true500); // 普通購買, 無優(yōu)惠券

          鏈式

          其他參考


          關(guān)于本文

          作者:hannie76327

          https://juejin.cn/post/6953872475537014820


          瀏覽 61
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产无圣光 | 俺来也俺也啪 | 操逼莫熊 | 最新欧美成人在线观看 | 日韩伦理色片一区二区 |