前端常見(jiàn)的9種設(shè)計(jì)模式

本文目錄:

  • 1.概念
  • 2.設(shè)計(jì)原則
  • 3.設(shè)計(jì)模式的類(lèi)型
  • 前端常見(jiàn)設(shè)計(jì)模式1:外觀模式(Facade Pattern)
  • 前端常見(jiàn)設(shè)計(jì)模式2:代理模式(Proxy Pattern)
  • 前端常見(jiàn)設(shè)計(jì)模式3:工廠(chǎng)模式(Factory Pattern)
  • 前端常見(jiàn)設(shè)計(jì)模式4:單例模式(Singleton Pattern)
  • 前端常見(jiàn)設(shè)計(jì)模式5:策略模式(Strategy Pattern)
  • 前端常見(jiàn)設(shè)計(jì)模式6:迭代器模式(Iterator Pattern)
  • 前端常見(jiàn)設(shè)計(jì)模式7:觀察者模式(Observer Pattern)
  • 前端常見(jiàn)設(shè)計(jì)模式8:中介者模式(Mediator Pattern)
  • 前端常見(jiàn)設(shè)計(jì)模式9:訪(fǎng)問(wèn)者模式(Visitor Pattern)

1. 概念

設(shè)計(jì)模式是一套被反復(fù)使用的漓摩、多數(shù)人知曉的拦英、經(jīng)過(guò)分類(lèi)編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)拇泛。使用設(shè)計(jì)模式是為了重用代碼、讓代碼更容易被他人理解思灌、保證代碼可靠性俺叭。毫無(wú)疑問(wèn),設(shè)計(jì)模式于己于他人于系統(tǒng)都是多贏的泰偿,設(shè)計(jì)模式使代碼編制真正工程化熄守,設(shè)計(jì)模式是軟件工程的基石,如同大廈的一塊塊磚石一樣耗跛。

2. 設(shè)計(jì)原則

  • S – Single Responsibility Principle 單一職責(zé)原則
    一個(gè)程序只做好一件事
    如果功能過(guò)于復(fù)雜就拆分開(kāi)裕照,每個(gè)部分保持獨(dú)立
  • O – OpenClosed Principle 開(kāi)放/封閉原則
    對(duì)擴(kuò)展開(kāi)放,對(duì)修改封閉
    增加需求時(shí)调塌,擴(kuò)展新代碼晋南,而非修改已有代碼
  • L – Liskov Substitution Principle 里氏替換原則
    子類(lèi)能覆蓋父類(lèi)
    父類(lèi)能出現(xiàn)的地方子類(lèi)就能出現(xiàn)
  • I – Interface Segregation Principle 接口隔離原則
    保持接口的單一獨(dú)立
    類(lèi)似單一職責(zé)原則,這里更關(guān)注接口
  • D – Dependency Inversion Principle 依賴(lài)倒轉(zhuǎn)原則
    面向接口編程烟阐,依賴(lài)于抽象而不依賴(lài)于具
    使用方只關(guān)注接口而不關(guān)注具體類(lèi)的實(shí)現(xiàn)

3. 設(shè)計(jì)模式的類(lèi)型

  • 1.結(jié)構(gòu)型模式(Structural Patterns): 通過(guò)識(shí)別系統(tǒng)中組件間的簡(jiǎn)單關(guān)系來(lái)簡(jiǎn)化系統(tǒng)的設(shè)計(jì)搬俊。
  • 2.創(chuàng)建型模式(Creational Patterns): 處理對(duì)象的創(chuàng)建,根據(jù)實(shí)際情況使用合適的方式創(chuàng)建對(duì)象蜒茄。常規(guī)的對(duì)象創(chuàng)建方式可能會(huì)導(dǎo)致設(shè)計(jì)上的問(wèn)題唉擂,或增加設(shè)計(jì)的復(fù)雜度。創(chuàng)建型模式通過(guò)以某種方式控制對(duì)象的創(chuàng)建來(lái)解決問(wèn)題檀葛。
  • 3.行為型模式(Behavioral Patterns): 用于識(shí)別對(duì)象之間常見(jiàn)的交互模式并加以實(shí)現(xiàn)玩祟,如此,增加了這些交互的靈活性屿聋。

4.前端常見(jiàn)設(shè)計(jì)模式1:外觀模式(Facade Pattern)

外觀模式是最常見(jiàn)的設(shè)計(jì)模式之一空扎,它為子系統(tǒng)中的一組接口提供一個(gè)統(tǒng)一的高層接口,使子系統(tǒng)更容易使用润讥。簡(jiǎn)而言之外觀設(shè)計(jì)模式就是把多個(gè)子系統(tǒng)中復(fù)雜邏輯進(jìn)行抽象转锈,從而提供一個(gè)更統(tǒng)一、更簡(jiǎn)潔楚殿、更易用的API撮慨。很多我們常用的框架和庫(kù)基本都遵循了外觀設(shè)計(jì)模式,比如JQuery就把復(fù)雜的原生DOM操作進(jìn)行了抽象和封裝,并消除了瀏覽器之間的兼容問(wèn)題砌溺,從而提供了一個(gè)更高級(jí)更易用的版本影涉。其實(shí)在平時(shí)工作中我們也會(huì)經(jīng)常用到外觀模式進(jìn)行開(kāi)發(fā),只是我們不自知而已规伐。
兼容瀏覽器事件綁定

let 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
    }
};

封裝接口

let myEvent = {
    // ...
    stop: e => {
        e.stopPropagation();
        e.preventDefault();
    }
};
場(chǎng)景

設(shè)計(jì)初期蟹倾,應(yīng)該要有意識(shí)地將不同的兩個(gè)層分離,比如經(jīng)典的三層結(jié)構(gòu)猖闪,在數(shù)據(jù)訪(fǎng)問(wèn)層和業(yè)務(wù)邏輯層鲜棠、業(yè)務(wù)邏輯層和表示層之間建立外觀Facade
在開(kāi)發(fā)階段,子系統(tǒng)往往因?yàn)椴粩嗟闹貥?gòu)演化而變得越來(lái)越復(fù)雜培慌,增加外觀Facade可以提供一個(gè)簡(jiǎn)單的接口岔留,減少他們之間的依賴(lài)。
在維護(hù)一個(gè)遺留的大型系統(tǒng)時(shí)检柬,可能這個(gè)系統(tǒng)已經(jīng)很難維護(hù)了,這時(shí)候使用外觀Facade也是非常合適的竖配,為系系統(tǒng)開(kāi)發(fā)一個(gè)外觀Facade類(lèi)何址,為設(shè)計(jì)粗糙和高度復(fù)雜的遺留代碼提供比較清晰的接口,讓新系統(tǒng)和Facade對(duì)象交互进胯,F(xiàn)acade與遺留代碼交互所有的復(fù)雜工作用爪。

優(yōu)點(diǎn)
  • 減少系統(tǒng)相互依賴(lài)。
  • 提高靈活性胁镐。
  • 提高了安全性
缺點(diǎn)

不符合開(kāi)閉原則偎血,如果要改東西很麻煩,繼承重寫(xiě)都不合適盯漂。

前端常見(jiàn)設(shè)計(jì)模式2:代理模式(Proxy Pattern)

是為一個(gè)對(duì)象提供一個(gè)代用品或占位符颇玷,以便控制對(duì)它的訪(fǎng)問(wèn)
假設(shè)當(dāng)A 在心情好的時(shí)候收到花,小明表白成功的幾率有 60%就缆,而當(dāng)A 在心情差的時(shí)候收到花帖渠,小明表白的成功率無(wú)限趨近于0。小明跟A 剛剛認(rèn)識(shí)兩天竭宰,還無(wú)法辨別A 什么時(shí)候心情好空郊。如果不合時(shí)宜地把花送給A,花 被直接扔掉的可能性很大切揭,這束花可是小明吃了7 天泡面換來(lái)的狞甚。但是A 的朋友B 卻很了解A,所以小明只管把花交給B廓旬,B 會(huì)監(jiān)聽(tīng)A 的心情變化哼审,然后選 擇A 心情好的時(shí)候把花轉(zhuǎn)交給A,代碼如下:

let Flower = function() {}
let xiaoming = {
  sendFlower: function(target) {
    let flower = new Flower()
    target.receiveFlower(flower)
  }
}
let B = {
  receiveFlower: function(flower) {
    A.listenGoodMood(function() {
      A.receiveFlower(flower)
    })
  }
}
let A = {
  receiveFlower: function(flower) {
    console.log('收到花'+ flower)
  },
  listenGoodMood: function(fn) {
    setTimeout(function() {
      fn()
    }, 1000)
  }
}
xiaoming.sendFlower(B)
場(chǎng)景

HTML元 素事件代理

<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
<script>
  let ul = document.querySelector('#ul');
  ul.addEventListener('click', event => {
    console.log(event.target);
  });
</script>

ES6 的 proxy 阮一峰Proxy
jQuery.proxy()方法

優(yōu)點(diǎn)
  • 代理模式能將代理對(duì)象與被調(diào)用對(duì)象分離,降低了系統(tǒng)的耦合度棺蛛。代理模式在客戶(hù)端和目標(biāo)對(duì)象之間起到一個(gè)中介作用怔蚌,這樣可以起到保護(hù)目標(biāo)對(duì)象的作用
  • 代理對(duì)象可以擴(kuò)展目標(biāo)對(duì)象的功能;通過(guò)修改代理對(duì)象就可以了旁赊,符合開(kāi)閉原則桦踊;
缺點(diǎn)

處理請(qǐng)求速度可能有差別,非直接訪(fǎng)問(wèn)存在開(kāi)銷(xiāo)

前端常見(jiàn)設(shè)計(jì)模式3:工廠(chǎng)模式(Factory Pattern)

工廠(chǎng)模式定義一個(gè)用于創(chuàng)建對(duì)象的接口终畅,這個(gè)接口由子類(lèi)決定實(shí)例化哪一個(gè)類(lèi)籍胯。該模式使一個(gè)類(lèi)的實(shí)例化延遲到了子類(lèi)。而子類(lèi)可以重寫(xiě)接口方法以便創(chuàng)建的時(shí)候指定自己的對(duì)象類(lèi)型离福。

class Product {
    constructor(name) {
        this.name = name
    }
    init() {
        console.log('init')
    }
    fun() {
        console.log('fun')
    }
}
class Factory {
    create(name) {
        return new Product(name)
    }
}
// use
let factory = new Factory()
let p = factory.create('p1')
p.init()
p.fun()
場(chǎng)景
  • 如果你不想讓某個(gè)子系統(tǒng)與較大的那個(gè)對(duì)象之間形成強(qiáng)耦合杖狼,而是想運(yùn)行時(shí)從許多子系統(tǒng)中進(jìn)行挑選的話(huà),那么工廠(chǎng)模式是一個(gè)理想的選擇
  • 將new操作簡(jiǎn)單封裝妖爷,遇到new的時(shí)候就應(yīng)該考慮是否用工廠(chǎng)模式蝶涩;
  • 需要依賴(lài)具體環(huán)境創(chuàng)建不同實(shí)例,這些實(shí)例都有相同的行為,這時(shí)候我們可以使用工廠(chǎng)模式絮识,簡(jiǎn)化實(shí)現(xiàn)的過(guò)程绿聘,同時(shí)也可以減少每種對(duì)象所需的代碼量,有利于消除對(duì)象間的耦合次舌,提供更大的靈活性
優(yōu)點(diǎn)
  • 創(chuàng)建對(duì)象的過(guò)程可能很復(fù)雜熄攘,但我們只需要關(guān)心創(chuàng)建結(jié)果。
  • 構(gòu)造函數(shù)和創(chuàng)建者分離, 符合“開(kāi)閉原則”
  • 一個(gè)調(diào)用者想創(chuàng)建一個(gè)對(duì)象彼念,只要知道其名稱(chēng)就可以了挪圾。
  • 擴(kuò)展性高,如果想增加一個(gè)產(chǎn)品逐沙,只要擴(kuò)展一個(gè)工廠(chǎng)類(lèi)就可以哲思。
缺點(diǎn)
  • 添加新產(chǎn)品時(shí),需要編寫(xiě)新的具體產(chǎn)品類(lèi),一定程度上增加了系統(tǒng)的復(fù)雜度
  • 考慮到系統(tǒng)的可擴(kuò)展性酱吝,需要引入抽象層也殖,在客戶(hù)端代碼中均使用抽象層進(jìn)行定義,增加了系統(tǒng)的抽象性和理解難度
什么時(shí)候不用
  • 當(dāng)被應(yīng)用到錯(cuò)誤的問(wèn)題類(lèi)型上時(shí),這一模式會(huì)給應(yīng)用程序引入大量不必要的復(fù)雜性.除非為創(chuàng)建對(duì)象提供一個(gè)接口是我們編寫(xiě)的庫(kù)或者框架的一個(gè)設(shè)計(jì)上目標(biāo),否則我會(huì)建議使用明確的構(gòu)造器,以避免不必要的開(kāi)銷(xiāo)务热。
  • 由于對(duì)象的創(chuàng)建過(guò)程被高效的抽象在一個(gè)接口后面的事實(shí),這也會(huì)給依賴(lài)于這個(gè)過(guò)程可能會(huì)有多復(fù)雜的單元測(cè)試帶來(lái)問(wèn)題忆嗜。

前端常見(jiàn)設(shè)計(jì)模式4:單例模式(Singleton Pattern)

顧名思義,單例模式中Class的實(shí)例個(gè)數(shù)最多為1崎岂。當(dāng)需要一個(gè)對(duì)象去貫穿整個(gè)系統(tǒng)執(zhí)行某些任務(wù)時(shí)捆毫,單例模式就派上了用場(chǎng)。而除此之外的場(chǎng)景盡量避免單例模式的使用冲甘,因?yàn)閱卫J綍?huì)引入全局狀態(tài)绩卤,而一個(gè)健康的系統(tǒng)應(yīng)該避免引入過(guò)多的全局狀態(tài)途样。
實(shí)現(xiàn)單例模式需要解決以下幾個(gè)問(wèn)題:

  • 如何確定Class只有一個(gè)實(shí)例?
  • 如何簡(jiǎn)便的訪(fǎng)問(wèn)Class的唯一實(shí)例濒憋?
  • Class如何控制實(shí)例化的過(guò)程何暇?
  • 如何將Class的實(shí)例個(gè)數(shù)限制為1?

我們一般通過(guò)實(shí)現(xiàn)以下兩點(diǎn)來(lái)解決上述問(wèn)題:
1.隱藏Class的構(gòu)造函數(shù)凛驮,避免多次實(shí)例化
2.通過(guò)暴露一個(gè) getInstance() 方法來(lái)創(chuàng)建/獲取唯一實(shí)例
Javascript中單例模式可以通過(guò)以下方式實(shí)現(xiàn):

// 單例構(gòu)造器
const FooServiceSingleton = (function () {
  // 隱藏的Class的構(gòu)造函數(shù)
  function FooService() {}
  // 未初始化的單例對(duì)象
  let fooService;
  return {
    // 創(chuàng)建/獲取單例對(duì)象的函數(shù)
    getInstance: function () {
      if (!fooService) {
        fooService = new FooService();
      }
      return fooService;
    }
  }
})();

實(shí)現(xiàn)的關(guān)鍵點(diǎn)有:

  • 使用 IIFE創(chuàng)建局部作用域并即時(shí)執(zhí)行裆站;
  • getInstance()為一個(gè) 閉包 ,使用閉包保存局部作用域中的單例對(duì)象并返回黔夭。

我們可以驗(yàn)證下單例對(duì)象是否創(chuàng)建成功:

const fooService1 = FooServiceSingleton.getInstance();
const fooService2 = FooServiceSingleton.getInstance();
console.log(fooService1 === fooService2); // true
場(chǎng)景
  • 定義命名空間和實(shí)現(xiàn)分支型方法
  • 登錄框
  • vuex 和 redux中的store
優(yōu)點(diǎn)
  • 劃分命名空間宏胯,減少全局變量
  • 增強(qiáng)模塊性,把自己的代碼組織在一個(gè)全局變量名下本姥,放在單一位置肩袍,便于維護(hù)
  • 且只會(huì)實(shí)例化一次。簡(jiǎn)化了代碼的調(diào)試和維護(hù)
缺點(diǎn)

由于單例模式提供的是一種單點(diǎn)訪(fǎng)問(wèn)婚惫,所以它有可能導(dǎo)致模塊間的強(qiáng)耦合
從而不利于單元測(cè)試氛赐。無(wú)法單獨(dú)測(cè)試一個(gè)調(diào)用了來(lái)自單例的方法的類(lèi),而只能把它與那個(gè)單例作為一個(gè)單元一起測(cè)試先舷。

前端常見(jiàn)設(shè)計(jì)模式5:策略模式(Strategy Pattern)

策略模式簡(jiǎn)單描述就是:對(duì)象有某個(gè)行為鹰祸,但是在不同的場(chǎng)景中,該行為有不同的實(shí)現(xiàn)算法密浑。把它們一個(gè)個(gè)封裝起來(lái),并且使它們可以互相替換

<html>
<head>
    <title>策略模式-校驗(yàn)表單</title>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
</head>
<body>
    <form id = "registerForm" method="post" action="http://xxxx.com/api/register">
        用戶(hù)名:<input type="text" name="userName">
        密碼:<input type="text" name="password">
        手機(jī)號(hào)碼:<input type="text" name="phoneNumber">
        <button type="submit">提交</button>
    </form>
    <script type="text/javascript">
        // 策略對(duì)象
        const strategies = {
          isNoEmpty: function (value, errorMsg) {
            if (value === '') {
              return errorMsg;
            }
          },
          isNoSpace: function (value, errorMsg) {
            if (value.trim() === '') {
              return errorMsg;
            }
          },
          minLength: function (value, length, errorMsg) {
            if (value.trim().length < length) {
              return errorMsg;
            }
          },
          maxLength: function (value, length, errorMsg) {
            if (value.length > length) {
              return errorMsg;
            }
          },
          isMobile: function (value, errorMsg) {
            if (!/^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[7]|18[0|1|2|3|5|6|7|8|9])\d{8}$/.test(value)) {
              return errorMsg;
            }                
          }
        }
        // 驗(yàn)證類(lèi)
        class Validator {
          constructor() {
            this.cache = []
          }
          add(dom, rules) {
            for(let i = 0, rule; rule = rules[i++];) {
              let strategyAry = rule.strategy.split(':')
              let errorMsg = rule.errorMsg
              this.cache.push(() => {
                let strategy = strategyAry.shift()
                strategyAry.unshift(dom.value)
                strategyAry.push(errorMsg)
                return strategies[strategy].apply(dom, strategyAry)
              })
            }
          }
          start() {
            for(let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
              let errorMsg = validatorFunc()
              if (errorMsg) {
                return errorMsg
              }
            }
          }
        }
        // 調(diào)用代碼
        let registerForm = document.getElementById('registerForm')

        let validataFunc = function() {
          let validator = new Validator()
          validator.add(registerForm.userName, [{
            strategy: 'isNoEmpty',
            errorMsg: '用戶(hù)名不可為空'
          }, {
            strategy: 'isNoSpace',
            errorMsg: '不允許以空白字符命名'
          }, {
            strategy: 'minLength:2',
            errorMsg: '用戶(hù)名長(zhǎng)度不能小于2位'
          }])
          validator.add(registerForm.password, [ {
            strategy: 'minLength:6',
            errorMsg: '密碼長(zhǎng)度不能小于6位'
          }])
          validator.add(registerForm.phoneNumber, [{
            strategy: 'isMobile',
            errorMsg: '請(qǐng)輸入正確的手機(jī)號(hào)碼格式'
          }])
          return validator.start()
        }

        registerForm.onsubmit = function() {
          let errorMsg = validataFunc()
          if (errorMsg) {
            alert(errorMsg)
            return false
          }
        }
</script>
</body>
</html>
場(chǎng)景
  • 如果在一個(gè)系統(tǒng)里面有許多類(lèi)粗井,它們之間的區(qū)別僅在于它們的'行為'尔破,那么-
    使用策略模式可以動(dòng)態(tài)地讓一個(gè)對(duì)象在許多行為中選擇一種行為。
  • 一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種浇衬。
  • 表單驗(yàn)證
優(yōu)點(diǎn)
  • 利用組合懒构、委托、多態(tài)等技術(shù)和思想耘擂,可以有效的避免多重條件選擇語(yǔ)句
  • 提供了對(duì)開(kāi)放-封閉原則的完美支持胆剧,將算法封裝在獨(dú)立的strategy中,使得它們易于切換醉冤,理解秩霍,易于擴(kuò)展
  • 利用組合和委托來(lái)讓Context擁有執(zhí)行算法的能力,這也是繼承的一種更輕便的代替方案
缺點(diǎn)
  • 會(huì)在程序中增加許多策略類(lèi)或者策略對(duì)象
  • 要使用策略模式蚁阳,必須了解所有的strategy铃绒,必須了解各個(gè)strategy之間的不同點(diǎn),這樣才能選擇一個(gè)合適的strategy

前端常見(jiàn)設(shè)計(jì)模式6:迭代器模式(Iterator Pattern)

迭代器模式簡(jiǎn)單的說(shuō)就是提供一種方法順序一個(gè)聚合對(duì)象中各個(gè)元素螺捐,而又不暴露該對(duì)象的內(nèi)部表示颠悬。
迭代器模式解決了以下問(wèn)題:

  • 提供一致的遍歷各種數(shù)據(jù)結(jié)構(gòu)的方式矮燎,而不用了解數(shù)據(jù)的內(nèi)部結(jié)構(gòu)
  • 提供遍歷容器(集合)的能力而無(wú)需改變?nèi)萜鞯慕涌?/li>

一個(gè)迭代器通常需要實(shí)現(xiàn)以下接口:

  • hasNext():判斷迭代是否結(jié)束,返回Boolean
  • next():查找并返回下一個(gè)元素

為Javascript的數(shù)組實(shí)現(xiàn)一個(gè)迭代器可以這么寫(xiě):

const item = [1, 'red', false, 3.14];
function Iterator(items) {
  this.items = items;
  this.index = 0;
}
Iterator.prototype = {
  hasNext: function () {
    return this.index < this.items.length;
  },
  next: function () {
    return this.items[this.index++];
  }
}

驗(yàn)證一下迭代器是否工作:

const iterator = new Iterator(item);
while(iterator.hasNext()){
  console.log(iterator.next());
}
//輸出:1, red, false, 3.14

ES6提供了更簡(jiǎn)單的迭代循環(huán)語(yǔ)法 for...of赔癌,使用該語(yǔ)法的前提是操作對(duì)象需要實(shí)現(xiàn) 可迭代協(xié)議(The iterable protocol)池充,簡(jiǎn)單說(shuō)就是該對(duì)象有個(gè)Key為 Symbol.iterator 的方法桶蝎,該方法返回一個(gè)iterator對(duì)象。
比如我們實(shí)現(xiàn)一個(gè) Range 類(lèi)用于在某個(gè)數(shù)字區(qū)間進(jìn)行迭代:

function Range(start, end) {
  return {
    [Symbol.iterator]: function () {
      return {
        next() {
          if (start < end) {
            return { value: start++, done: false };
          }
          return { done: true, value: end };
        }
      }
    }
  }
}

驗(yàn)證一下:

for (num of Range(1, 5)) {
  console.log(num);
}
// 輸出:1, 2, 3, 4

前端常見(jiàn)設(shè)計(jì)模式7:觀察者模式(Observer Pattern)

觀察者模式又稱(chēng)發(fā)布-訂閱模式(Publish/Subscribe Pattern),是我們經(jīng)常接觸到的設(shè)計(jì)模式设凹,日常生活中的應(yīng)用也比比皆是,比如你訂閱了某個(gè)博主的頻道涎嚼,當(dāng)有內(nèi)容更新時(shí)會(huì)收到推送杉辙;又比如JavaScript中的事件訂閱響應(yīng)機(jī)制。觀察者模式的思想用一句話(huà)描述就是:被觀察對(duì)象(subject)維護(hù)一組觀察者(observer)班缰,當(dāng)被觀察對(duì)象狀態(tài)改變時(shí)贤壁,通過(guò)調(diào)用觀察者的某個(gè)方法將這些變化通知到觀察者。
觀察者模式中Subject對(duì)象一般需要實(shí)現(xiàn)以下API:

  • subscribe(): 接收一個(gè)觀察者observer對(duì)象埠忘,使其訂閱自己
  • unsubscribe(): 接收一個(gè)觀察者observer對(duì)象脾拆,使其取消訂閱自己
  • fire(): 觸發(fā)事件,通知到所有觀察者

用JavaScript手動(dòng)實(shí)現(xiàn)觀察者模式:

// 被觀察者
function Subject() {
  this.observers = [];
}

Subject.prototype = {
  // 訂閱
  subscribe: function (observer) {
    this.observers.push(observer);
  },
  // 取消訂閱
  unsubscribe: function (observerToRemove) {
    this.observers = this.observers.filter(observer => {
      return observer !== observerToRemove;
    })
  },
  // 事件觸發(fā)
  fire: function () {
    this.observers.forEach(observer => {
      observer.call();
    });
  }
}

驗(yàn)證一下訂閱是否成功:

const subject = new Subject();
function observer1() {
  console.log('Observer 1 Firing!');
}
function observer2() {
  console.log('Observer 2 Firing!');
}
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.fire();
//輸出:
Observer 1 Firing! 
Observer 2 Firing!

驗(yàn)證一下取消訂閱是否成功:

subject.unsubscribe(observer2);
subject.fire();
//輸出:
Observer 1 Firing!
場(chǎng)景

DOM事件

document.body.addEventListener('click', function() {
    console.log('hello world!');
});
document.body.click()

vue 響應(yīng)式的實(shí)現(xiàn)

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

支持簡(jiǎn)單的廣播通信莹妒,自動(dòng)通知所有已經(jīng)訂閱過(guò)的對(duì)象
目標(biāo)對(duì)象與觀察者之間的抽象耦合關(guān)系能單獨(dú)擴(kuò)展以及重用
增加了靈活性
觀察者模式所做的工作就是在解耦名船,讓耦合的雙方都依賴(lài)于抽象,而不是依賴(lài)于具體旨怠。從而使得各自的變化都不會(huì)影響到另一邊的變化渠驼。

缺點(diǎn)

過(guò)度使用會(huì)導(dǎo)致對(duì)象與對(duì)象之間的聯(lián)系弱化,會(huì)導(dǎo)致程序難以跟蹤維護(hù)和理解

前端常見(jiàn)設(shè)計(jì)模式8:中介者模式(Mediator Pattern)

在中介者模式中鉴腻,中介者(Mediator)包裝了一系列對(duì)象相互作用的方式迷扇,使得這些對(duì)象不必直接相互作用,而是由中介者協(xié)調(diào)它們之間的交互爽哎,從而使它們可以松散偶合蜓席。當(dāng)某些對(duì)象之間的作用發(fā)生改變時(shí),不會(huì)立即影響其他的一些對(duì)象之間的作用课锌,保證這些作用可以彼此獨(dú)立的變化厨内。
中介者模式和觀察者模式有一定的相似性,都是一對(duì)多的關(guān)系渺贤,也都是集中式通信雏胃,不同的是中介者模式是處理同級(jí)對(duì)象之間的交互,而觀察者模式是處理Observer和Subject之間的交互志鞍。中介者模式有些像婚戀中介丑掺,相親對(duì)象剛開(kāi)始并不能直接交流,而是要通過(guò)中介去篩選匹配再?zèng)Q定誰(shuí)和誰(shuí)見(jiàn)面述雾。

場(chǎng)景

例如購(gòu)物車(chē)需求街州,存在商品選擇表單兼丰、顏色選擇表單、購(gòu)買(mǎi)數(shù)量表單等等唆缴,都會(huì)觸發(fā)change事件鳍征,那么可以通過(guò)中介者來(lái)轉(zhuǎn)發(fā)處理這些事件,實(shí)現(xiàn)各個(gè)事件間的解耦面徽,僅僅維護(hù)中介者對(duì)象即可艳丛。

var goods = {   //手機(jī)庫(kù)存
    'red|32G': 3,
    'red|64G': 1,
    'blue|32G': 7,
    'blue|32G': 6,
};
//中介者
var mediator = (function() {
    var colorSelect = document.getElementById('colorSelect');
    var memorySelect = document.getElementById('memorySelect');
    var numSelect = document.getElementById('numSelect');
    return {
        changed: function(obj) {
            switch(obj){
                case colorSelect:
                    //TODO
                    break;
                case memorySelect:
                    //TODO
                    break;
                case numSelect:
                    //TODO
                    break;
            }
        }
    }
})();
colorSelect.onchange = function() {
    mediator.changed(this);
};
memorySelect.onchange = function() {
    mediator.changed(this);
};
numSelect.onchange = function() {
    mediator.changed(this);
};

聊天室里
聊天室成員類(lèi):

function Member(name) {
  this.name = name;
  this.chatroom = null;
}

Member.prototype = {
  // 發(fā)送消息
  send: function (message, toMember) {
    this.chatroom.send(message, this, toMember);
  },
  // 接收消息
  receive: function (message, fromMember) {
    console.log(`${fromMember.name} to ${this.name}: ${message}`);
  }
}

聊天室類(lèi):

function Chatroom() {
  this.members = {};
}
Chatroom.prototype = {
  // 增加成員
  addMember: function (member) {
    this.members[member.name] = member;
    member.chatroom = this;
  },
  // 發(fā)送消息
  send: function (message, fromMember, toMember) {
    toMember.receive(message, fromMember);
  }
}

測(cè)試一下:

const chatroom = new Chatroom();
const bruce = new Member('bruce');
const frank = new Member('frank');
chatroom.addMember(bruce);
chatroom.addMember(frank);
bruce.send('Hey frank', frank);
//輸出:bruce to frank: hello frank
優(yōu)點(diǎn)
  • 使各對(duì)象之間耦合松散,而且可以獨(dú)立地改變它們之間的交互
  • 中介者和對(duì)象一對(duì)多的關(guān)系取代了對(duì)象之間的網(wǎng)狀多對(duì)多的關(guān)系
  • 如果對(duì)象之間的復(fù)雜耦合度導(dǎo)致維護(hù)很困難趟紊,而且耦合度隨項(xiàng)目變化增速很快氮双,就需要中介者重構(gòu)代碼
缺點(diǎn)

系統(tǒng)中會(huì)新增一個(gè)中介者對(duì)象,因?yàn)閷?duì)象之間交互的復(fù)雜性霎匈,轉(zhuǎn)移成了中介者對(duì)象的復(fù)雜性戴差,使得中介者對(duì)象經(jīng)常是巨大的。中介 者對(duì)象自身往往就是一個(gè)難以維護(hù)的對(duì)象铛嘱。

前端常見(jiàn)設(shè)計(jì)模式9:訪(fǎng)問(wèn)者模式(Visitor Pattern)

訪(fǎng)問(wèn)者模式 是一種將算法與對(duì)象結(jié)構(gòu)分離的設(shè)計(jì)模式暖释,通俗點(diǎn)講就是:訪(fǎng)問(wèn)者模式讓我們能夠在不改變一個(gè)對(duì)象結(jié)構(gòu)的前提下能夠給該對(duì)象增加新的邏輯,新增的邏輯保存在一個(gè)獨(dú)立的訪(fǎng)問(wèn)者對(duì)象中墨吓。訪(fǎng)問(wèn)者模式常用于拓展一些第三方的庫(kù)和工具球匕。

// 訪(fǎng)問(wèn)者  
class Visitor {
    constructor() {}
    visitConcreteElement(ConcreteElement) {
        ConcreteElement.operation()
    }
}
// 元素類(lèi)  
class ConcreteElement{
    constructor() {
    }
    operation() {
       console.log("ConcreteElement.operation invoked");  
    }
    accept(visitor) {
        visitor.visitConcreteElement(this)
    }
}
// client
let visitor = new Visitor()
let element = new ConcreteElement()
elementA.accept(visitor)

訪(fǎng)問(wèn)者模式的實(shí)現(xiàn)有以下幾個(gè)要素:

  • Visitor Object:訪(fǎng)問(wèn)者對(duì)象,擁有一個(gè)visit()方法
  • Receiving Object:接收對(duì)象帖烘,擁有一個(gè)accept() 方法
  • visit(receivingObj):用于Visitor接收一個(gè)Receiving Object
  • accept(visitor):用于Receving Object接收一個(gè)Visitor亮曹,并通過(guò)調(diào)用Visitor的 visit() 為其提供獲取Receiving Object數(shù)據(jù)的能力

簡(jiǎn)單的代碼實(shí)現(xiàn)如下:

Receiving Object:
function Employee(name, salary) {
  this.name = name;
  this.salary = salary;
}
Employee.prototype = {
  getSalary: function () {
    return this.salary;
  },
  setSalary: function (salary) {
    this.salary = salary;
  },
  accept: function (visitor) {
    visitor.visit(this);
  }
}
Visitor Object:
function Visitor() { }
Visitor.prototype = {
  visit: function (employee) {
    employee.setSalary(employee.getSalary() * 2);
  }
}

驗(yàn)證一下:

const employee = new Employee('bruce', 1000);
const visitor = new Visitor();
employee.accept(visitor);
console.log(employee.getSalary());//輸出:2000
場(chǎng)景
  • 對(duì)象結(jié)構(gòu)中對(duì)象對(duì)應(yīng)的類(lèi)很少改變,但經(jīng)常需要在此對(duì)象結(jié)構(gòu)上定義新的操作
  • 需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作秘症,而需要避免讓這些操作"污染"這些對(duì)象的類(lèi)乾忱,也不希望在增加新操作時(shí)修改這些類(lèi)。
優(yōu)點(diǎn)
  • 符合單一職責(zé)原則
  • 優(yōu)秀的擴(kuò)展性
  • 靈活性
缺點(diǎn)
  • 具體元素對(duì)訪(fǎng)問(wèn)者公布細(xì)節(jié)历极,違反了迪米特原則
  • 違反了依賴(lài)倒置原則,依賴(lài)了具體類(lèi)衷佃,沒(méi)有依賴(lài)抽象趟卸。
  • 具體元素變更比較困難
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市氏义,隨后出現(xiàn)的幾起案子锄列,更是在濱河造成了極大的恐慌,老刑警劉巖惯悠,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邻邮,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡克婶,警方通過(guò)查閱死者的電腦和手機(jī)筒严,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)丹泉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鸭蛙,你說(shuō)我怎么就攤上這事摹恨。” “怎么了娶视?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵晒哄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我肪获,道長(zhǎng)寝凌,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任孝赫,我火速辦了婚禮较木,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寒锚。我一直安慰自己劫映,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布刹前。 她就那樣靜靜地躺著泳赋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喇喉。 梳的紋絲不亂的頭發(fā)上祖今,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音拣技,去河邊找鬼千诬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛膏斤,可吹牛的內(nèi)容都是我干的徐绑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼莫辨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼傲茄!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起沮榜,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盘榨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蟆融,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體草巡,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年型酥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了山憨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片查乒。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖萍歉,靈堂內(nèi)的尸體忽然破棺而出侣颂,到底是詐尸還是另有隱情,我是刑警寧澤枪孩,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布憔晒,位于F島的核電站,受9級(jí)特大地震影響蔑舞,放射性物質(zhì)發(fā)生泄漏拒担。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一攻询、第九天 我趴在偏房一處隱蔽的房頂上張望从撼。 院中可真熱鬧,春花似錦钧栖、人聲如沸低零。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)掏婶。三九已至,卻和暖如春潭陪,著一層夾襖步出監(jiān)牢的瞬間雄妥,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工依溯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留老厌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓黎炉,卻偏偏與公主長(zhǎng)得像枝秤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子慷嗜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345