本文目錄:
- 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)抽象趟卸。
- 具體元素變更比較困難