設(shè)計(jì)模式簡(jiǎn)介
設(shè)計(jì)模式代表了最佳的實(shí)踐,通常被有經(jīng)驗(yàn)的面向?qū)ο蟮能浖_(kāi)發(fā)人員所采用。設(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)的痰憎。
設(shè)計(jì)模式是一套被反復(fù)使用的、多數(shù)人知曉的攀涵、經(jīng)過(guò)分類編目的信殊、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。使用設(shè)計(jì)模式是為了重用代碼汁果、讓代碼更容易被他人理解涡拘、保證代碼可靠性。 毫無(wú)疑問(wèn)据德,設(shè)計(jì)模式于己于他人于系統(tǒng)都是多贏的鳄乏,設(shè)計(jì)模式使代碼編制真正工程化紧卒,設(shè)計(jì)模式是軟件工程的基石饭弓,如同大廈的一塊塊磚石一樣。
設(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 里氏替換原則
- 子類能覆蓋父類
- 父類能出現(xiàn)的地方子類就能出現(xiàn)
- I – Interface Segregation Principle 接口隔離原則
- 保持接口的單一獨(dú)立
- 類似單一職責(zé)原則茅郎,這里更關(guān)注接口
- D – Dependency Inversion Principle 依賴倒轉(zhuǎn)原則
- 面向接口編程蜗元,依賴于抽象而不依賴于具體
- 使用方只關(guān)注接口而不關(guān)注具體類的實(shí)現(xiàn)
SO體現(xiàn)較多,舉個(gè)栗子:(比如Promise)
- 單一職責(zé)原則:每個(gè)then中的邏輯只做好一件事
- 開(kāi)放封閉原則(對(duì)擴(kuò)展開(kāi)放系冗,對(duì)修改封閉):如果新增需求奕扣,擴(kuò)展then
再舉個(gè)栗子:(此例來(lái)源-守候-改善代碼的各方面問(wèn)題)
//checkType('165226226326','mobile')
//result:false
let checkType=function(str, type) {
switch (type) {
case 'email':
return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str)
case 'mobile':
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
case 'tel':
return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
default:
return true;
}
}
有以下兩個(gè)問(wèn)題:
- 如果想添加其他規(guī)則就得在函數(shù)里面增加 case 。添加一個(gè)規(guī)則就修改一次掌敬!這樣違反了開(kāi)放-封閉原則(對(duì)擴(kuò)展開(kāi)放惯豆,對(duì)修改關(guān)閉)。而且這樣也會(huì)導(dǎo)致整個(gè) API 變得臃腫奔害,難維護(hù)楷兽。
- 比如A頁(yè)面需要添加一個(gè)金額的校驗(yàn),B頁(yè)面需要一個(gè)日期的校驗(yàn)华临,但是金額的校驗(yàn)只在A頁(yè)面需要芯杀,日期的校驗(yàn)只在B頁(yè)面需要。如果一直添加 case 。就是導(dǎo)致A頁(yè)面把只在B頁(yè)面需要的校驗(yàn)規(guī)則也添加進(jìn)去瘪匿,造成不必要的開(kāi)銷跛梗。B頁(yè)面也同理。
建議的方式是給這個(gè) API 增加一個(gè)擴(kuò)展的接口:
let checkType=(function(){
let rules={
email(str){
return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
},
mobile(str){
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
}
};
//暴露接口
return {
//校驗(yàn)
check(str, type){
return rules[type]?rules[type](str):false;
},
//添加規(guī)則
addRule(type,fn){
rules[type]=fn;
}
}
})();
//調(diào)用方式
//使用mobile校驗(yàn)規(guī)則
console.log(checkType.check('188170239','mobile'));
//添加金額校驗(yàn)規(guī)則
checkType.addRule('money',function (str) {
return /^[0-9]+(.[0-9]{2})?$/.test(str)
});
//使用金額校驗(yàn)規(guī)則
console.log(checkType.check('18.36','money'));
此例更詳細(xì)內(nèi)容請(qǐng)查看-> 守候i-重構(gòu)-改善代碼的各方面問(wèn)題
設(shè)計(jì)模式分類(23種設(shè)計(jì)模式)
- 創(chuàng)建型
- 單例模式
- 原型模式
- 工廠模式
- 抽象工廠模式
- 建造者模式
- 結(jié)構(gòu)型
- 適配器模式
- 裝飾器模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 行為型
- 觀察者模式
- 迭代器模式
- 策略模式
- 模板方法模式
- 職責(zé)鏈模式
- 命令模式
- 備忘錄模式
- 狀態(tài)模式
- 訪問(wèn)者模式
- 中介者模式
- 解釋器模式
工廠模式
工廠模式定義一個(gè)用于創(chuàng)建對(duì)象的接口棋弥,這個(gè)接口由子類決定實(shí)例化哪一個(gè)類核偿。該模式使一個(gè)類的實(shí)例化延遲到了子類。而子類可以重寫接口方法以便創(chuàng)建的時(shí)候指定自己的對(duì)象類型顽染。
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)行挑選的話,那么工廠模式是一個(gè)理想的選擇
- 將new操作簡(jiǎn)單封裝粉寞,遇到new的時(shí)候就應(yīng)該考慮是否用工廠模式尼荆;
- 需要依賴具體環(huán)境創(chuàng)建不同實(shí)例,這些實(shí)例都有相同的行為,這時(shí)候我們可以使用工廠模式唧垦,簡(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ì)象坊秸,只要知道其名稱就可以了麸祷。
- 擴(kuò)展性高,如果想增加一個(gè)產(chǎn)品褒搔,只要擴(kuò)展一個(gè)工廠類就可以阶牍。
缺點(diǎn)
- 添加新產(chǎn)品時(shí),需要編寫新的具體產(chǎn)品類,一定程度上增加了系統(tǒng)的復(fù)雜度
- 考慮到系統(tǒng)的可擴(kuò)展性星瘾,需要引入抽象層走孽,在客戶端代碼中均使用抽象層進(jìn)行定義,增加了系統(tǒng)的抽象性和理解難度
什么時(shí)候不用
當(dāng)被應(yīng)用到錯(cuò)誤的問(wèn)題類型上時(shí),這一模式會(huì)給應(yīng)用程序引入大量不必要的復(fù)雜性.除非為創(chuàng)建對(duì)象提供一個(gè)接口是我們編寫的庫(kù)或者框架的一個(gè)設(shè)計(jì)上目標(biāo),否則我會(huì)建議使用明確的構(gòu)造器,以避免不必要的開(kāi)銷死相。
由于對(duì)象的創(chuàng)建過(guò)程被高效的抽象在一個(gè)接口后面的事實(shí),這也會(huì)給依賴于這個(gè)過(guò)程可能會(huì)有多復(fù)雜的單元測(cè)試帶來(lái)問(wèn)題融求。
例子
- 曾經(jīng)我們熟悉的JQuery的$()就是一個(gè)工廠函數(shù)咬像,它根據(jù)傳入?yún)?shù)的不同創(chuàng)建元素或者去尋找上下文中的元素算撮,創(chuàng)建成相應(yīng)的jQuery對(duì)象
class jQuery {
constructor(selector) {
super(selector)
}
add() {
}
// 此處省略若干API
}
window.$ = function(selector) {
return new jQuery(selector)
}
- vue 的異步組件
在大型應(yīng)用中,我們可能需要將應(yīng)用分割成小一些的代碼塊县昂,并且只在需要的時(shí)候才從服務(wù)器加載一個(gè)模塊肮柜。為了簡(jiǎn)化,Vue 允許你以一個(gè)工廠函數(shù)的方式定義你的組件倒彰,這個(gè)工廠函數(shù)會(huì)異步解析你的組件定義审洞。Vue 只有在這個(gè)組件需要被渲染的時(shí)候才會(huì)觸發(fā)該工廠函數(shù),且會(huì)把結(jié)果緩存起來(lái)供未來(lái)重渲染。例如:
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回調(diào)傳遞組件定義
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
單例模式
一個(gè)類只有一個(gè)實(shí)例芒澜,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)仰剿。
class LoginForm {
constructor() {
this.state = 'hide'
}
show() {
if (this.state === 'show') {
alert('已經(jīng)顯示')
return
}
this.state = 'show'
console.log('登錄框顯示成功')
}
hide() {
if (this.state === 'hide') {
alert('已經(jīng)隱藏')
return
}
this.state = 'hide'
console.log('登錄框隱藏成功')
}
}
LoginForm.getInstance = (function () {
let instance
return function () {
if (!instance) {
instance = new LoginForm()
}
return instance
}
})()
let obj1 = LoginForm.getInstance()
obj1.show()
let obj2 = LoginForm.getInstance()
obj2.hide()
console.log(obj1 === obj2)
優(yōu)點(diǎn)
- 劃分命名空間,減少全局變量
- 增強(qiáng)模塊性痴晦,把自己的代碼組織在一個(gè)全局變量名下南吮,放在單一位置,便于維護(hù)
- 且只會(huì)實(shí)例化一次誊酌。簡(jiǎn)化了代碼的調(diào)試和維護(hù)
缺點(diǎn)
- 由于單例模式提供的是一種單點(diǎn)訪問(wèn)部凑,所以它有可能導(dǎo)致模塊間的強(qiáng)耦合 從而不利于單元測(cè)試。無(wú)法單獨(dú)測(cè)試一個(gè)調(diào)用了來(lái)自單例的方法的類碧浊,而只能把它與那個(gè)單例作為一個(gè)單元一起測(cè)試涂邀。
場(chǎng)景例子
- 定義命名空間和實(shí)現(xiàn)分支型方法
- 登錄框
- vuex 和 redux中的store
適配器模式
將一個(gè)類的接口轉(zhuǎn)化為另外一個(gè)接口,以滿足用戶需求箱锐,使類之間接口不兼容問(wèn)題通過(guò)適配器得以解決比勉。
class Plug {
getName() {
return 'iphone充電頭';
}
}
class Target {
constructor() {
this.plug = new Plug();
}
getName() {
return this.plug.getName() + ' 適配器Type-c充電頭';
}
}
let target = new Target();
target.getName(); // iphone充電頭 適配器轉(zhuǎn)Type-c充電頭
優(yōu)點(diǎn)
- 可以讓任何兩個(gè)沒(méi)有關(guān)聯(lián)的類一起運(yùn)行。
- 提高了類的復(fù)用驹止。
- 適配對(duì)象敷搪,適配庫(kù),適配數(shù)據(jù)
缺點(diǎn)
- 額外對(duì)象的創(chuàng)建幢哨,非直接調(diào)用赡勘,存在一定的開(kāi)銷(且不像代理模式在某些功能點(diǎn)上可實(shí)現(xiàn)性能優(yōu)化)
- 如果沒(méi)必要使用適配器模式的話,可以考慮重構(gòu)捞镰,如果使用的話闸与,盡量把文檔完善
場(chǎng)景
- 整合第三方SDK
- 封裝舊接口
// 自己封裝的ajax, 使用方式如下
ajax({
url: '/getData',
type: 'Post',
dataType: 'json',
data: {
test: 111
}
}).done(function() {})
// 因?yàn)闅v史原因岸售,代碼中全都是:
// $.ajax({....})
// 做一層適配器
var $ = {
ajax: function (options) {
return ajax(options)
}
}
- vue的computed
<template>
<div id="example">
<p>Original message: "{{ message }}"</p> <!-- Hello -->
<p>Computed reversed message: "{{ reversedMessage }}"</p> <!-- olleH -->
</div>
</template>
<script type='text/javascript'>
export default {
name: 'demo',
data() {
return {
message: 'Hello'
}
},
computed: {
reversedMessage: function() {
return this.message.split('').reverse().join('')
}
}
}
</script>
原有data 中的數(shù)據(jù)不滿足當(dāng)前的要求践樱,通過(guò)計(jì)算屬性的規(guī)則來(lái)適配成我們需要的格式,對(duì)原有數(shù)據(jù)并沒(méi)有改變凸丸,只改變了原有數(shù)據(jù)的表現(xiàn)形式
不同點(diǎn)
適配器與代理模式相似
- 適配器模式: 提供一個(gè)不同的接口(如不同版本的插頭)
- 代理模式: 提供一模一樣的接口
裝飾者模式
- 動(dòng)態(tài)地給某個(gè)對(duì)象添加一些額外的職責(zé)拷邢,,是一種實(shí)現(xiàn)繼承的替代方案
- 在不改變?cè)瓕?duì)象的基礎(chǔ)上屎慢,通過(guò)對(duì)其進(jìn)行包裝擴(kuò)展瞭稼,使原有對(duì)象可以滿足用戶的更復(fù)雜需求,而不會(huì)影響從這個(gè)類中派生的其他對(duì)象
class Cellphone {
create() {
console.log('生成一個(gè)手機(jī)')
}
}
class Decorator {
constructor(cellphone) {
this.cellphone = cellphone
}
create() {
this.cellphone.create()
this.createShell(cellphone)
}
createShell() {
console.log('生成手機(jī)殼')
}
}
// 測(cè)試代碼
let cellphone = new Cellphone()
cellphone.create()
console.log('------------')
let dec = new Decorator(cellphone)
dec.create()
場(chǎng)景例子
- 比如現(xiàn)在有4 種型號(hào)的自行車腻惠,我們?yōu)槊糠N自行車都定義了一個(gè)單 獨(dú)的類』分猓現(xiàn)在要給每種自行車都裝上前燈、尾 燈和鈴鐺這3 種配件集灌。如果使用繼承的方式來(lái)給 每種自行車創(chuàng)建子類悔雹,則需要 4×3 = 12 個(gè)子類。 但是如果把前燈、尾燈腌零、鈴鐺這些對(duì)象動(dòng)態(tài)組 合到自行車上面梯找,則只需要額外增加3 個(gè)類
- ES7 Decorator 阮一峰
- core-decorators
優(yōu)點(diǎn)
- 裝飾類和被裝飾類都只關(guān)心自身的核心業(yè)務(wù),實(shí)現(xiàn)了解耦益涧。
- 方便動(dòng)態(tài)的擴(kuò)展功能初肉,且提供了比繼承更多的靈活性。
缺點(diǎn)
- 多層裝飾比較復(fù)雜饰躲。
- 常常會(huì)引入許多小對(duì)象牙咏,看起來(lái)比較相似,實(shí)際功能大相徑庭嘹裂,從而使得我們的應(yīng)用程序架構(gòu)變得復(fù)雜起來(lái)
代理模式
是為一個(gè)對(duì)象提供一個(gè)代用品或占位符妄壶,以便控制對(duì)它的訪問(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)的耦合度团搞。代理模式在客戶端和目標(biāo)對(duì)象之間起到一個(gè)中介作用,這樣可以起到保護(hù)目標(biāo)對(duì)象的作用
- 代理對(duì)象可以擴(kuò)展目標(biāo)對(duì)象的功能多艇;通過(guò)修改代理對(duì)象就可以了逻恐,符合開(kāi)閉原則;
缺點(diǎn)
處理請(qǐng)求速度可能有差別峻黍,非直接訪問(wèn)存在開(kāi)銷
不同點(diǎn)
裝飾者模式實(shí)現(xiàn)上和代理模式類似
- 裝飾者模式: 擴(kuò)展功能复隆,原有功能不變且可直接使用
- 代理模式: 顯示原有功能,但是經(jīng)過(guò)限制之后的
外觀模式
為子系統(tǒng)的一組接口提供一個(gè)一致的界面奸披,定義了一個(gè)高層接口昏名,這個(gè)接口使子系統(tǒng)更加容易使用
- 兼容瀏覽器事件綁定
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ù)訪問(wèn)層和業(yè)務(wù)邏輯層、業(yè)務(wù)邏輯層和表示層之間建立外觀Facade
- 在開(kāi)發(fā)階段样刷,子系統(tǒng)往往因?yàn)椴粩嗟闹貥?gòu)演化而變得越來(lái)越復(fù)雜仑扑,增加外觀Facade可以提供一個(gè)簡(jiǎn)單的接口,減少他們之間的依賴置鼻。
- 在維護(hù)一個(gè)遺留的大型系統(tǒng)時(shí)镇饮,可能這個(gè)系統(tǒng)已經(jīng)很難維護(hù)了,這時(shí)候使用外觀Facade也是非常合適的箕母,為系系統(tǒng)開(kāi)發(fā)一個(gè)外觀Facade類储藐,為設(shè)計(jì)粗糙和高度復(fù)雜的遺留代碼提供比較清晰的接口,讓新系統(tǒng)和Facade對(duì)象交互嘶是,F(xiàn)acade與遺留代碼交互所有的復(fù)雜工作钙勃。
參考: 大話設(shè)計(jì)模式
優(yōu)點(diǎn)
- 減少系統(tǒng)相互依賴。
- 提高靈活性聂喇。
- 提高了安全性
缺點(diǎn)
- 不符合開(kāi)閉原則辖源,如果要改東西很麻煩,繼承重寫都不合適希太。
觀察者模式
定義了一種一對(duì)多的關(guān)系克饶,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽(tīng)某一個(gè)主題對(duì)象,這個(gè)主題對(duì)象的狀態(tài)發(fā)生變化時(shí)就會(huì)通知所有的觀察者對(duì)象誊辉,使它們能夠自動(dòng)更新自己矾湃,當(dāng)一個(gè)對(duì)象的改變需要同時(shí)改變其它對(duì)象,并且它不知道具體有多少對(duì)象需要改變的時(shí)候堕澄,就應(yīng)該考慮使用觀察者模式洲尊。
- 發(fā)布 & 訂閱
- 一對(duì)多
// 主題 保存狀態(tài),狀態(tài)變化之后觸發(fā)所有觀察者對(duì)象
class Subject {
constructor() {
this.state = 0
this.observers = []
}
getState() {
return this.state
}
setState(state) {
this.state = state
this.notifyAllObservers()
}
notifyAllObservers() {
this.observers.forEach(observer => {
observer.update()
})
}
attach(observer) {
this.observers.push(observer)
}
}
// 觀察者
class Observer {
constructor(name, subject) {
this.name = name
this.subject = subject
this.subject.attach(this)
}
update() {
console.log(`${this.name} update, state: ${this.subject.getState()}`)
}
}
// 測(cè)試
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('02', s)
s.setState(12)
場(chǎng)景
- DOM事件
document.body.addEventListener('click', function() {
console.log('hello world!');
});
document.body.click()
- vue 響應(yīng)式
優(yōu)點(diǎn)
- 支持簡(jiǎn)單的廣播通信奈偏,自動(dòng)通知所有已經(jīng)訂閱過(guò)的對(duì)象
- 目標(biāo)對(duì)象與觀察者之間的抽象耦合關(guān)系能單獨(dú)擴(kuò)展以及重用
- 增加了靈活性
- 觀察者模式所做的工作就是在解耦坞嘀,讓耦合的雙方都依賴于抽象,而不是依賴于具體惊来。從而使得各自的變化都不會(huì)影響到另一邊的變化丽涩。
缺點(diǎn)
過(guò)度使用會(huì)導(dǎo)致對(duì)象與對(duì)象之間的聯(lián)系弱化,會(huì)導(dǎo)致程序難以跟蹤維護(hù)和理解
狀態(tài)模式
允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變的時(shí)候改變它的行為裁蚁,對(duì)象看起來(lái)似乎修改了它的類
// 狀態(tài) (弱光矢渊、強(qiáng)光、關(guān)燈)
class State {
constructor(state) {
this.state = state
}
handle(context) {
console.log(`this is ${this.state} light`)
context.setState(this)
}
}
class Context {
constructor() {
this.state = null
}
getState() {
return this.state
}
setState(state) {
this.state = state
}
}
// test
let context = new Context()
let weak = new State('weak')
let strong = new State('strong')
let off = new State('off')
// 弱光
weak.handle(context)
console.log(context.getState())
// 強(qiáng)光
strong.handle(context)
console.log(context.getState())
// 關(guān)閉
strong.handle(context)
console.log(context.getState())
場(chǎng)景
- 一個(gè)對(duì)象的行為取決于它的狀態(tài)枉证,并且它必須在運(yùn)行時(shí)刻根據(jù)狀態(tài)改變它的行為
- 一個(gè)操作中含有大量的分支語(yǔ)句矮男,而且這些分支語(yǔ)句依賴于該對(duì)象的狀態(tài)
優(yōu)點(diǎn)
- 定義了狀態(tài)與行為之間的關(guān)系,封裝在一個(gè)類里室谚,更直觀清晰毡鉴,增改方便
- 狀態(tài)與狀態(tài)間崔泵,行為與行為間彼此獨(dú)立互不干擾
- 用對(duì)象代替字符串來(lái)記錄當(dāng)前狀態(tài),使得狀態(tài)的切換更加一目了然
缺點(diǎn)
- 會(huì)在系統(tǒng)中定義許多狀態(tài)類
- 邏輯分散
迭代器模式
提供一種方法順序一個(gè)聚合對(duì)象中各個(gè)元素猪瞬,而又不暴露該對(duì)象的內(nèi)部表示憎瘸。
class Iterator {
constructor(conatiner) {
this.list = conatiner.list
this.index = 0
}
next() {
if (this.hasNext()) {
return this.list[this.index++]
}
return null
}
hasNext() {
if (this.index >= this.list.length) {
return false
}
return true
}
}
class Container {
constructor(list) {
this.list = list
}
getIterator() {
return new Iterator(this)
}
}
// 測(cè)試代碼
let container = new Container([1, 2, 3, 4, 5])
let iterator = container.getIterator()
while(iterator.hasNext()) {
console.log(iterator.next())
}
場(chǎng)景例子
- Array.prototype.forEach
- jQuery中的$.each()
- ES6 Iterator
特點(diǎn)
- 訪問(wèn)一個(gè)聚合對(duì)象的內(nèi)容而無(wú)需暴露它的內(nèi)部表示。
- 為遍歷不同的集合結(jié)構(gòu)提供一個(gè)統(tǒng)一的接口陈瘦,從而支持同樣的算法在不同的集合結(jié)構(gòu)上進(jìn)行操作
總結(jié)
對(duì)于集合內(nèi)部結(jié)果常常變化各異幌甘,不想暴露其內(nèi)部結(jié)構(gòu)的話,但又想讓客戶代碼透明的訪問(wèn)其中的元素痊项,可以使用迭代器模式
橋接模式
橋接模式(Bridge)將抽象部分與它的實(shí)現(xiàn)部分分離锅风,使它們都可以獨(dú)立地變化。
class Color {
constructor(name){
this.name = name
}
}
class Shape {
constructor(name,color){
this.name = name
this.color = color
}
draw(){
console.log(`${this.color.name} ${this.name}`)
}
}
//測(cè)試
let red = new Color('red')
let yellow = new Color('yellow')
let circle = new Shape('circle', red)
circle.draw()
let triangle = new Shape('triangle', yellow)
triangle.draw()
優(yōu)點(diǎn)
- 有助于獨(dú)立地管理各組成部分鞍泉, 把抽象化與實(shí)現(xiàn)化解耦
- 提高可擴(kuò)充性
缺點(diǎn)
- 大量的類將導(dǎo)致開(kāi)發(fā)成本的增加皱埠,同時(shí)在性能方面可能也會(huì)有所減少。
組合模式
- 將對(duì)象組合成樹(shù)形結(jié)構(gòu)塞弊,以表示“整體-部分”的層次結(jié)構(gòu)漱逸。
- 通過(guò)對(duì)象的多態(tài)表現(xiàn),使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性游沿。
class TrainOrder {
create () {
console.log('創(chuàng)建火車票訂單')
}
}
class HotelOrder {
create () {
console.log('創(chuàng)建酒店訂單')
}
}
class TotalOrder {
constructor () {
this.orderList = []
}
addOrder (order) {
this.orderList.push(order)
return this
}
create () {
this.orderList.forEach(item => {
item.create()
})
return this
}
}
// 可以在購(gòu)票網(wǎng)站買車票同時(shí)也訂房間
let train = new TrainOrder()
let hotel = new HotelOrder()
let total = new TotalOrder()
total.addOrder(train).addOrder(hotel).create()
場(chǎng)景
- 表示對(duì)象-整體層次結(jié)構(gòu)
- 希望用戶忽略組合對(duì)象和單個(gè)對(duì)象的不同饰抒,用戶將統(tǒng)一地使用組合結(jié)構(gòu)中的所有對(duì)象(方法)
缺點(diǎn)
如果通過(guò)組合模式創(chuàng)建了太多的對(duì)象,那么這些對(duì)象可能會(huì)讓系統(tǒng)負(fù)擔(dān)不起诀黍。
原型模式
原型模式(prototype)是指用原型實(shí)例指向創(chuàng)建對(duì)象的種類袋坑,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。
class Person {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class Student extends Person {
constructor(name) {
super(name)
}
sayHello() {
console.log(`Hello眯勾, My name is ${this.name}`)
}
}
let student = new Student("xiaoming")
student.sayHello()
原型模式枣宫,就是創(chuàng)建一個(gè)共享的原型,通過(guò)拷貝這個(gè)原型來(lái)創(chuàng)建新的類吃环,用于創(chuàng)建重復(fù)的對(duì)象也颤,帶來(lái)性能上的提升。
策略模式
定義一系列的算法郁轻,把它們一個(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">
用戶名:<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)證類
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: '用戶名不可為空'
}, {
strategy: 'isNoSpace',
errorMsg: '不允許以空白字符命名'
}, {
strategy: 'minLength:2',
errorMsg: '用戶名長(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)里面有許多類,它們之間的區(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ì)在程序中增加許多策略類或者策略對(duì)象
- 要使用策略模式译柏,必須了解所有的strategy镣煮,必須了解各個(gè)strategy之間的不同點(diǎn)姐霍,這樣才能選擇一個(gè)合適的strategy
享元模式
運(yùn)用共享技術(shù)有效地支持大量細(xì)粒度對(duì)象的復(fù)用。系統(tǒng)只使用少量的對(duì)象典唇,而這些對(duì)象都很相似镊折,狀態(tài)變化很小,可以實(shí)現(xiàn)對(duì)象的多次復(fù)用介衔。由于享元模式要求能夠共享的對(duì)象必須是細(xì)粒度對(duì)象恨胚,因此它又稱為輕量級(jí)模式,它是一種對(duì)象結(jié)構(gòu)型模式
let examCarNum = 0 // 駕考車總數(shù)
/* 駕考車對(duì)象 */
class ExamCar {
constructor(carType) {
examCarNum++
this.carId = examCarNum
this.carType = carType ? '手動(dòng)檔' : '自動(dòng)檔'
this.usingState = false // 是否正在使用
}
/* 在本車上考試 */
examine(candidateId) {
return new Promise((resolve => {
this.usingState = true
console.log(`考生- ${ candidateId } 開(kāi)始在${ this.carType }駕考車- ${ this.carId } 上考試`)
setTimeout(() => {
this.usingState = false
console.log(`%c考生- ${ candidateId } 在${ this.carType }駕考車- ${ this.carId } 上考試完畢`, 'color:#f40')
resolve() // 0~2秒后考試完畢
}, Math.random() * 2000)
}))
}
}
/* 手動(dòng)檔汽車對(duì)象池 */
ManualExamCarPool = {
_pool: [], // 駕考車對(duì)象池
_candidateQueue: [], // 考生隊(duì)列
/* 注冊(cè)考生 ID 列表 */
registCandidates(candidateList) {
candidateList.forEach(candidateId => this.registCandidate(candidateId))
},
/* 注冊(cè)手動(dòng)檔考生 */
registCandidate(candidateId) {
const examCar = this.getManualExamCar() // 找一個(gè)未被占用的手動(dòng)檔駕考車
if (examCar) {
examCar.examine(candidateId) // 開(kāi)始考試炎咖,考完了讓隊(duì)列中的下一個(gè)考生開(kāi)始考試
.then(() => {
const nextCandidateId = this._candidateQueue.length && this._candidateQueue.shift()
nextCandidateId && this.registCandidate(nextCandidateId)
})
} else this._candidateQueue.push(candidateId)
},
/* 注冊(cè)手動(dòng)檔車 */
initManualExamCar(manualExamCarNum) {
for (let i = 1; i <= manualExamCarNum; i++) {
this._pool.push(new ExamCar(true))
}
},
/* 獲取狀態(tài)為未被占用的手動(dòng)檔車 */
getManualExamCar() {
return this._pool.find(car => !car.usingState)
}
}
ManualExamCarPool.initManualExamCar(3) // 一共有3個(gè)駕考車
ManualExamCarPool.registCandidates([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) // 10個(gè)考生來(lái)考試
場(chǎng)景例子
- 文件上傳需要?jiǎng)?chuàng)建多個(gè)文件實(shí)例的時(shí)候
- 如果一個(gè)應(yīng)用程序使用了大量的對(duì)象赃泡,而這些大量的對(duì)象造成了很大的存儲(chǔ)開(kāi)銷時(shí)就應(yīng)該考慮使用享元模式
優(yōu)點(diǎn)
- 大大減少對(duì)象的創(chuàng)建,降低系統(tǒng)的內(nèi)存乘盼,使效率提高升熊。
缺點(diǎn)
- 提高了系統(tǒng)的復(fù)雜度,需要分離出外部狀態(tài)和內(nèi)部狀態(tài)绸栅,而且外部狀態(tài)具有固有化的性質(zhì)级野, 不應(yīng)該隨著內(nèi)部狀態(tài)的變化而變化,否則會(huì)造成系統(tǒng)的混亂
模板方法模式
模板方法模式由兩部分結(jié)構(gòu)組成粹胯,第一部分是抽象父類蓖柔,第二部分是具體的實(shí)現(xiàn)子類。通常在抽象父類中封裝了子類的算法框架风纠,包括實(shí)現(xiàn)一些公共方法和封裝子類中所有方法的執(zhí)行順序况鸣。子類通過(guò)繼承這個(gè)抽象類,也繼承了整個(gè)算法結(jié)構(gòu)竹观,并且可以選擇重寫父類的方法镐捧。
class Beverage {
constructor({brewDrink, addCondiment}) {
this.brewDrink = brewDrink
this.addCondiment = addCondiment
}
/* 燒開(kāi)水,共用方法 */
boilWater() { console.log('水已經(jīng)煮沸=== 共用') }
/* 倒杯子里栈幸,共用方法 */
pourCup() { console.log('倒進(jìn)杯子里===共用') }
/* 模板方法 */
init() {
this.boilWater()
this.brewDrink()
this.pourCup()
this.addCondiment()
}
}
/* 咖啡 */
const coffee = new Beverage({
/* 沖泡咖啡愤估,覆蓋抽象方法 */
brewDrink: function() { console.log('沖泡咖啡') },
/* 加調(diào)味品,覆蓋抽象方法 */
addCondiment: function() { console.log('加點(diǎn)奶和糖') }
})
coffee.init()
場(chǎng)景例子
- 一次性實(shí)現(xiàn)一個(gè)算法的不變的部分速址,并將可變的行為留給子類來(lái)實(shí)現(xiàn)
- 子類中公共的行為應(yīng)被提取出來(lái)并集中到一個(gè)公共父類中的避免代碼重復(fù)
優(yōu)點(diǎn)
- 提取了公共代碼部分玩焰,易于維護(hù)
缺點(diǎn)
- 增加了系統(tǒng)復(fù)雜度,主要是增加了的抽象類和類間聯(lián)系
職責(zé)鏈模式
使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求芍锚,從而避免請(qǐng)求的發(fā)送者和接受者之間的耦合關(guān)系昔园,將這些對(duì)象連成一條鏈蔓榄,并沿著這條鏈傳遞該請(qǐng)求,直到有一個(gè)對(duì)象處理它為止
// 請(qǐng)假審批默刚,需要組長(zhǎng)審批甥郑、經(jīng)理審批、總監(jiān)審批
class Action {
constructor(name) {
this.name = name
this.nextAction = null
}
setNextAction(action) {
this.nextAction = action
}
handle() {
console.log( `${this.name} 審批`)
if (this.nextAction != null) {
this.nextAction.handle()
}
}
}
let a1 = new Action("組長(zhǎng)")
let a2 = new Action("經(jīng)理")
let a3 = new Action("總監(jiān)")
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()
場(chǎng)景例子
- JS 中的事件冒泡
- 作用域鏈
- 原型鏈
優(yōu)點(diǎn)
- 降低耦合度荤西。它將請(qǐng)求的發(fā)送者和接收者解耦澜搅。
- 簡(jiǎn)化了對(duì)象。使得對(duì)象不需要知道鏈的結(jié)構(gòu)
- 增強(qiáng)給對(duì)象指派職責(zé)的靈活性邪锌。通過(guò)改變鏈內(nèi)的成員或者調(diào)動(dòng)它們的次序勉躺,允許動(dòng)態(tài)地新增或者刪除責(zé)任
- 增加新的請(qǐng)求處理類很方便。
缺點(diǎn)
- 不能保證某個(gè)請(qǐng)求一定會(huì)被鏈中的節(jié)點(diǎn)處理觅丰,這種情況可以在鏈尾增加一個(gè)保底的接受者節(jié)點(diǎn)來(lái)處理這種即將離開(kāi)鏈尾的請(qǐng)求饵溅。
- 使程序中多了很多節(jié)點(diǎn)對(duì)象,可能再一次請(qǐng)求的過(guò)程中妇萄,大部分的節(jié)點(diǎn)并沒(méi)有起到實(shí)質(zhì)性的作用蜕企。他們的作用僅僅是讓請(qǐng)求傳遞下去,從性能當(dāng)面考慮冠句,要避免過(guò)長(zhǎng)的職責(zé)鏈到來(lái)的性能損耗轻掩。
命令模式
將一個(gè)請(qǐng)求封裝成一個(gè)對(duì)象,從而讓你使用不同的請(qǐng)求把客戶端參數(shù)化轩端,對(duì)請(qǐng)求排隊(duì)或者記錄請(qǐng)求日志放典,可以提供命令的撤銷和恢復(fù)功能。
// 接收者類
class Receiver {
execute() {
console.log('接收者執(zhí)行請(qǐng)求')
}
}
// 命令者
class Command {
constructor(receiver) {
this.receiver = receiver
}
execute () {
console.log('命令');
this.receiver.execute()
}
}
// 觸發(fā)者
class Invoker {
constructor(command) {
this.command = command
}
invoke() {
console.log('開(kāi)始')
this.command.execute()
}
}
// 倉(cāng)庫(kù)
const warehouse = new Receiver();
// 訂單
const order = new Command(warehouse);
// 客戶
const client = new Invoker(order);
client.invoke()
優(yōu)點(diǎn)
- 對(duì)命令進(jìn)行封裝基茵,使命令易于擴(kuò)展和修改
- 命令發(fā)出者和接受者解耦奋构,使發(fā)出者不需要知道命令的具體執(zhí)行過(guò)程即可執(zhí)行
缺點(diǎn)
- 使用命令模式可能會(huì)導(dǎo)致某些系統(tǒng)有過(guò)多的具體命令類。
備忘錄模式
在不破壞封裝性的前提下拱层,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài)弥臼,并在該對(duì)象之外保存這個(gè)狀態(tài)。這樣以后就可將該對(duì)象恢復(fù)到保存的狀態(tài)根灯。
//備忘類
class Memento{
constructor(content){
this.content = content
}
getContent(){
return this.content
}
}
// 備忘列表
class CareTaker {
constructor(){
this.list = []
}
add(memento){
this.list.push(memento)
}
get(index){
return this.list[index]
}
}
// 編輯器
class Editor {
constructor(){
this.content = null
}
setContent(content){
this.content = content
}
getContent(){
return this.content
}
saveContentToMemento(){
return new Memento(this.content)
}
getContentFromMemento(memento){
this.content = memento.getContent()
}
}
//測(cè)試代碼
let editor = new Editor()
let careTaker = new CareTaker()
editor.setContent('111')
editor.setContent('222')
careTaker.add(editor.saveContentToMemento())
editor.setContent('333')
careTaker.add(editor.saveContentToMemento())
editor.setContent('444')
console.log(editor.getContent()) //444
editor.getContentFromMemento(careTaker.get(1))
console.log(editor.getContent()) //333
editor.getContentFromMemento(careTaker.get(0))
console.log(editor.getContent()) //222
場(chǎng)景例子
- 分頁(yè)控件
- 撤銷組件
優(yōu)點(diǎn)
- 給用戶提供了一種可以恢復(fù)狀態(tài)的機(jī)制径缅,可以使用戶能夠比較方便地回到某個(gè)歷史的狀態(tài)
缺點(diǎn)
- 消耗資源。如果類的成員變量過(guò)多烙肺,勢(shì)必會(huì)占用比較大的資源纳猪,而且每一次保存都會(huì)消耗一定的內(nèi)存。
中介者模式
解除對(duì)象與對(duì)象之間的緊耦合關(guān)系桃笙。增加一個(gè)中介者對(duì)象后氏堤,所有的 相關(guān)對(duì)象都通過(guò)中介者對(duì)象來(lái)通信,而不是互相引用搏明,所以當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí)鼠锈,只需要通知 中介者對(duì)象即可闪檬。中介者使各對(duì)象之間耦合松散,而且可以獨(dú)立地改變它們之間的交互购笆。中介者 模式使網(wǎng)狀的多對(duì)多關(guān)系變成了相對(duì)簡(jiǎn)單的一對(duì)多關(guān)系(類似于觀察者模式粗悯,但是單向的,由中介者統(tǒng)一管理同欠。)
class A {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setB()
}
}
}
class B {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setA()
}
}
}
class Mediator {
constructor(a, b) {
this.a = a
this.b = b
}
setA() {
let number = this.b.number
this.a.setNumber(number * 10)
}
setB() {
let number = this.a.number
this.b.setNumber(number / 10)
}
}
let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(10, m)
console.log(a.number, b.number)
b.setNumber(10, m)
console.log(a.number, b.number)
場(chǎng)景例子
- 系統(tǒng)中對(duì)象之間存在比較復(fù)雜的引用關(guān)系样傍,導(dǎo)致它們之間的依賴關(guān)系結(jié)構(gòu)混亂而且難以復(fù)用該對(duì)象
- 想通過(guò)一個(gè)中間類來(lái)封裝多個(gè)類中的行為,而又不想生成太多的子類行您。
優(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ì)象,因 為對(duì)象之間交互的復(fù)雜性斗蒋,轉(zhuǎn)移成了中介者對(duì)象的復(fù)雜性捌斧,使得中介者對(duì)象經(jīng)常是巨大的。中介 者對(duì)象自身往往就是一個(gè)難以維護(hù)的對(duì)象泉沾。
解釋器模式
給定一個(gè)語(yǔ)言, 定義它的文法的一種表示捞蚂,并定義一個(gè)解釋器, 該解釋器使用該表示來(lái)解釋語(yǔ)言中的句子。
此例來(lái)自心譚博客
class Context {
constructor() {
this._list = []; // 存放 終結(jié)符表達(dá)式
this._sum = 0; // 存放 非終結(jié)符表達(dá)式(運(yùn)算結(jié)果)
}
get sum() {
return this._sum;
}
set sum(newValue) {
this._sum = newValue;
}
add(expression) {
this._list.push(expression);
}
get list() {
return [...this._list];
}
}
class PlusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = ++context.sum;
}
}
class MinusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = --context.sum;
}
}
/** 以下是測(cè)試代碼 **/
const context = new Context();
// 依次添加: 加法 | 加法 | 減法 表達(dá)式
context.add(new PlusExpression());
context.add(new PlusExpression());
context.add(new MinusExpression());
// 依次執(zhí)行: 加法 | 加法 | 減法 表達(dá)式
context.list.forEach(expression => expression.interpret(context));
console.log(context.sum);
優(yōu)點(diǎn)
- 易于改變和擴(kuò)展文法跷究。
- 由于在解釋器模式中使用類來(lái)表示語(yǔ)言的文法規(guī)則姓迅,因此可以通過(guò)繼承等機(jī)制來(lái)改變或擴(kuò)展文法
缺點(diǎn)
- 執(zhí)行效率較低,在解釋器模式中使用了大量的循環(huán)和遞歸調(diào)用俊马,因此在解釋較為復(fù)雜的句子時(shí)其速度慢
- 對(duì)于復(fù)雜的文法比較難維護(hù)
訪問(wèn)者模式
表示一個(gè)作用于某對(duì)象結(jié)構(gòu)中的各元素的操作丁存。它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作。
// 訪問(wèn)者
class Visitor {
constructor() {}
visitConcreteElement(ConcreteElement) {
ConcreteElement.operation()
}
}
// 元素類
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)
場(chǎng)景例子
- 對(duì)象結(jié)構(gòu)中對(duì)象對(duì)應(yīng)的類很少改變柴我,但經(jīng)常需要在此對(duì)象結(jié)構(gòu)上定義新的操作
- 需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作解寝,而需要避免讓這些操作"污染"這些對(duì)象的類,也不希望在增加新操作時(shí)修改這些類艘儒。
優(yōu)點(diǎn)
- 符合單一職責(zé)原則
- 優(yōu)秀的擴(kuò)展性
- 靈活性
缺點(diǎn)
- 具體元素對(duì)訪問(wèn)者公布細(xì)節(jié)聋伦,違反了迪米特原則
- 違反了依賴倒置原則,依賴了具體類界睁,沒(méi)有依賴抽象觉增。
- 具體元素變更比較困難
作者:九思
鏈接:https://juejin.im/post/5e021eb96fb9a01628014095
來(lái)源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)翻斟,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處逾礁。