javascript的23種設(shè)計模式示例總結(jié)大全
一、設(shè)計模式介紹
什么是設(shè)計模式
- 設(shè)計模式是解決問題的一種思想,和語言無關(guān)。在面向?qū)ο筌浖O(shè)計的工程中,針對特定的問題簡潔優(yōu)雅的一種解決方案。通俗一點的說,設(shè)計模式就是符合某種場景下某個問題的解決方案,通過設(shè)計模式可以增加代碼的可重用性,可擴(kuò)展性,可維護(hù)性,最終使得我們的代碼高內(nèi)聚、低耦合。
設(shè)計模式的五大設(shè)計原則
- 單一職責(zé):一個程序只需要做好一件事。如果功能過于復(fù)雜就拆分開,保證每個部分的獨立
- 開放封閉原則:對擴(kuò)展開放,對修改封閉。增加需求時,擴(kuò)展新代碼,而不是修改源代碼。這是軟件設(shè)計的終極目標(biāo)。
- 里氏置換原則:子類能覆蓋父類,父類能出現(xiàn)的地方子類也能出現(xiàn)。
- 接口獨立原則:保持接口的單一獨立,避免出現(xiàn)“胖接口”。這點目前在TS中運用到。
- 依賴導(dǎo)致原則:面向接口編程,依賴于抽象而不依賴于具體。使用方只專注接口而不用關(guān)注具體類的實現(xiàn)。俗稱“鴨子類型”
設(shè)計模式的三大類
- 創(chuàng)建型:工廠模式,抽象工廠模式,建造者模式,單例模式,原型模式
- 結(jié)構(gòu)型:適配器模式,裝飾器模式,代理模式,外觀模式,橋接模式,組合模式,享元模式
- 行為型:策略模式,模板方法模式,發(fā)布訂閱模式,迭代器模式,職責(zé)鏈模式,命令模式,備忘錄模式,狀態(tài)模式,訪問者模式,中介者模式,解釋器模式。
二、設(shè)計模式
1.工廠模式
- 工廠模式是用來創(chuàng)建對象的常見設(shè)計模式,在不暴露創(chuàng)建對象的具體邏輯,而是將邏輯進(jìn)行封裝,那么它就可以被稱為工廠。工廠模式又叫做靜態(tài)工廠模式,由一個工廠對象決定創(chuàng)建某一個類的實例。
優(yōu)點
- 調(diào)用者創(chuàng)建對象時只要知道其名稱即可
- 擴(kuò)展性高,如果要新增一個產(chǎn)品,直接擴(kuò)展一個工廠類即可。
- 隱藏產(chǎn)品的具體實現(xiàn),只關(guān)心產(chǎn)品的接口。
缺點
- 每次增加一個產(chǎn)品時,都需要增加一個具體類,這無形增加了系統(tǒng)內(nèi)存的壓力和系統(tǒng)的復(fù)雜度,也增加了具體類的依賴
例子
- 一個服裝廠可以生產(chǎn)不同類型的衣服,我們通過一個工廠方法類來模擬產(chǎn)出
class DownJacket {
production(){
console.log('生產(chǎn)羽絨服')
}
}
class Underwear{
production(){
console.log('生產(chǎn)內(nèi)衣')
}
}
class TShirt{
production(){
console.log('生產(chǎn)t恤')
}
}
// 工廠類
class clothingFactory {
constructor(){
this.downJacket = DownJacket
this.underwear = Underwear
this.t_shirt = TShirt
}
getFactory(clothingType){
const _production = new this[clothingType]
return _production.production()
}
}
const clothing = new clothingFactory()
clothing.getFactory('t_shirt')// 生產(chǎn)t恤
2.抽象工廠模式
- 抽象工廠模式就是通過類的抽象使得業(yè)務(wù)適用于一個產(chǎn)品類簇的創(chuàng)建,而不負(fù)責(zé)某一個類產(chǎn)品的實例。抽象工廠可以看作普通工廠的升級版,普通工廠以生產(chǎn)實例為主,而抽象工廠的目就是生產(chǎn)工廠。
優(yōu)點
- 當(dāng)一個產(chǎn)品族中的多個對象被設(shè)計成一起工作時,它能保證客戶端始終只使用同一個產(chǎn)品族中的對象。
缺點
- 產(chǎn)品族擴(kuò)展非常困難,要增加一個系列的某一產(chǎn)品,既要在抽象的 Creator 里加代碼,又要在具體的里面加代碼。
例子
- 同樣基于上面的例子,模擬出一個抽象類,同時約束繼承子類的方法實現(xiàn)。最后再通過工廠函數(shù)返回指定的類簇
/* 抽象類
js中abstract是個保留字,實現(xiàn)抽象類只能通過new.target進(jìn)行驗證,
防止抽象類被直接實例,另外如果子類沒有覆蓋指定方法,則拋出錯誤
*/
class ProductionFlow {
constructor(){
if(new.target === ProductionFlow){
throw new Error('抽象類不能被實例')
}
}
production(){
throw new Error('production要被重寫')
}
materials(){
throw new Error('materials要被重寫')
}
}
class DownJacket extends ProductionFlow{
production(){
console.log(`材料:${this.materials()},生產(chǎn)羽絨服`)
}
materials(){
return '鴨毛'
}
}
class Underwear extends ProductionFlow{
production(){
console.log(`材料:${this.materials()},生產(chǎn)內(nèi)衣`)
}
materials(){
return '絲光棉'
}
}
class TShirt extends ProductionFlow{
production(){
console.log(`材料:${this.materials()},生產(chǎn)t恤`)
}
materials(){
return '純棉'
}
}
function getAbstractProductionFactory(clothingType){
const clothingObj = {
downJacket:DownJacket,
underwear:Underwear,
t_shirt:TShirt,
}
if(clothingObj[clothingType]){
return clothingObj[clothingType]
}
throw new Error(`工廠暫時不支持生產(chǎn)這個${clothingType}類型的服裝`)
}
const downJacketClass = getAbstractProductionFactory('downJacket')
const underwearClass = getAbstractProductionFactory('underwear')
const downJacket = new downJacketClass()
const underwear = new underwearClass()
downJacket.production() // 材料:鴨毛,生產(chǎn)羽絨服
underwear.production() // 材料:絲光棉,生產(chǎn)內(nèi)衣
3.建造者模式
- 建造者模式是一種比較復(fù)雜使用頻率較低的創(chuàng)建型設(shè)計模式,建造者模式為客戶端返回的不是一個簡單的產(chǎn)品,而是一個由多個部件組成的復(fù)雜產(chǎn)品。主要用于將一個復(fù)雜對象的構(gòu)建與他的表現(xiàn)分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
優(yōu)點
- 建造者獨立易擴(kuò)展
- 方便控制細(xì)節(jié)風(fēng)險
缺點
- 產(chǎn)品必須有共同點,范圍有限制
- 當(dāng)內(nèi)部有變化復(fù)雜時,會有很多建造類
例子
下面繼續(xù)用服裝廠的生產(chǎn)流程作為例子。
// 抽象類
class Clothing {
constructor() {
this.clothingType = ''
this.price
}
}
class Underwear extends Clothing {
constructor() {
super()
this.clothingType = 'underwear'
this.price = 10
}
}
class TShirt extends Clothing {
constructor() {
super()
this.clothingType = 't_shirt'
this.price = 50
}
}
class DownCoat extends Clothing {
constructor() {
super()
this.clothingType = 'DownCoat'
this.price = 500
}
}
// 產(chǎn)品
class Purchase {
constructor() {
this.clothings = []
}
addClothing(clothing) {
this.clothings.push(clothing)
}
countPrice() {
return this.clothings.reduce((prev, cur)=>cur.price + prev,0)
}
}
// 廠長
class FactoryManager {
createUnderwear() {
throw new Error(`子類必須重寫 createUnderwear`)
}
createTShirt() {
throw new Error(`子類必須重寫 createTShirt`)
}
createDownCoat() {
throw new Error(`子類必須重寫 DownCoat`)
}
}
// 工人
class Worker extends FactoryManager {
constructor() {
super()
this.purchase = new Purchase()
}
createUnderwear(num) {
for (let i = 0; i < num; i++) {
this.purchase.addClothing(new Underwear())
}
}
createTShirt(num) {
for (let i = 0; i < num; i++) {
this.purchase.addClothing(new TShirt())
}
}
createDownCoat(num) {
for (let i = 0; i < num; i++) {
this.purchase.addClothing(new DownCoat())
}
}
}
// 銷售
class Salesman {
constructor() {
this.worker = null
}
setWorker(worker) {
this.worker = worker
}
reserve(clothing) {
clothing.forEach((item) => {
if (item.type === 'underwear') {
this.worker.createUnderwear(item.num)
} else if (item.type === 't_shirt') {
this.worker.createTShirt(item.num)
} else if (item.type === 'DownCoat') {
this.worker.createDownCoat(item.num)
} else {
try {
throw new Error('公司暫不生產(chǎn)或不存在該類型的商品')
} catch (error) {
console.log(error)
}
}
});
const purchase = this.worker.purchase
return purchase.countPrice()
}
}
const salesman = new Salesman()
const worker = new Worker()
salesman.setWorker(worker)
const order = [
{
type: 'underwear',
num: 10
},
{
type: 't_shirt',
num: 4
},
{
type: 'DownCoat',
num: 1
}
]
console.log(`本次訂單所需金額:${salesman.reserve(order)}`)
4.單例模式
- 單例模式的思路是:保證一個類只能被實例一次,每次獲取的時候,如果該類已經(jīng)創(chuàng)建過實例則直接返回該實例,否則創(chuàng)建一個實例保存并返回。
- 單例模式的核心就是創(chuàng)建一個唯一的對象,而在javascript中創(chuàng)建一個唯一的對象太簡單了,為了獲取一個對象而去創(chuàng)建一個類有點多此一舉。如
const obj = {},obj就是獨一無二的一個對象,在全局作用域的聲明下,可以在任何地方對它訪問,這就滿足了單例模式的條件。
優(yōu)點
- 內(nèi)存中只有一個實例,減少了內(nèi)存的開銷。
- 避免了對資源多重的占用。
缺點
- 違反了單一職責(zé),一個類應(yīng)該只關(guān)心內(nèi)部邏輯,而不用去關(guān)心外部的實現(xiàn)
例子
- 我們常見到的登錄彈窗,要么顯示要么隱藏,不可能同時出現(xiàn)兩個彈窗,下面我們通過一個類來模擬彈窗。
class LoginFrame {
static instance = null
constructor(state){
this.state = state
}
show(){
if(this.state === 'show'){
console.log('登錄框已顯示')
return
}
this.state = 'show'
console.log('登錄框展示成功')
}
hide(){
if(this.state === 'hide'){
console.log('登錄框已隱藏')
return
}
this.state = 'hide'
console.log('登錄框隱藏成功')
}
// 通過靜態(tài)方法獲取靜態(tài)屬性instance上是否存在實例,如果沒有創(chuàng)建一個并返回,反之直接返回已有的實例
static getInstance(state){
if(!this.instance){
this.instance = new LoginFrame(state)
}
return this.instance
}
}
const p1 = LoginFrame.getInstance('show')
const p2 = LoginFrame.getInstance('hide')
console.log(p1 === p2) // true
5.適配器模式
- 適配器模式的目的是為了解決對象之間的接口不兼容的問題,通過適配器模式可以不更改源代碼的情況下,讓兩個原本不兼容的對象在調(diào)用時正常工作。
優(yōu)點
- 讓任何兩個沒有關(guān)聯(lián)的類可以同時有效運行,并且提高了復(fù)用性、透明度、以及靈活性
缺點
- 過多的使用適配器模式,會讓系統(tǒng)變得零亂,不易整體把控。建議在無法重構(gòu)的情況下使用適配器。
例子
- 拿一個現(xiàn)實中的例子來說,杰克只會英語,小明只會中文,它們在交流上出現(xiàn)了障礙,小紅同時會中英雙語,通過小紅將杰克的英語翻譯成中文,讓小明和杰克進(jìn)行無障礙的溝通,這里小紅就起到了適配器的角色。
class Jack {
english() {
return 'I speak English'
}
}
class Xiaoming {
chinese() {
return '我只會中文'
}
}
// 適配器
class XiaoHong {
constructor(person) {
this.person = person
}
chinese() {
return `${this.person.english()} 翻譯: "我會說英語"`
}
}
class Communication {
speak(language) {
console.log(language.chinese())
}
}
const xiaoming = new Xiaoming()
const xiaoHong = new XiaoHong(new Jack())
const communication = new Communication()
communication.speak(xiaoming)
communication.speak(xiaoHong)
6.裝飾器模式
- 裝飾者模式能夠在不更改源代碼自身的情況下,對其進(jìn)行職責(zé)添加。相比于繼承裝飾器的做法更輕巧。通俗的講我們給心愛的手機(jī)上貼膜,帶手機(jī)殼,貼紙,這些就是對手機(jī)的裝飾。
優(yōu)點
- 裝飾類和被裝飾類它們之間可以相互獨立發(fā)展,不會相互耦合,裝飾器模式是繼承的一個替代模式,它可以動態(tài)的擴(kuò)展一個實現(xiàn)類的功能。
缺點
- 多層的裝飾會增加復(fù)雜度
例子
- 在編寫飛機(jī)大戰(zhàn)的游戲中,飛機(jī)對象的攻擊方式只有普通子彈攻擊,如何在不更改原代碼的情況下,為它其他的攻擊方式,如激光武器,導(dǎo)彈武器?
class Aircraft {
ordinary(){
console.log('發(fā)射普通子彈')
}
}
class AircraftDecorator {
constructor(aircraft){
this.aircraft = aircraft
}
laser(){
console.log('發(fā)射激光')
}
guidedMissile(){
console.log('發(fā)射導(dǎo)彈')
}
ordinary(){
this.aircraft.ordinary()
}
}
const aircraft = new Aircraft()
const aircraftDecorator = new AircraftDecorator(aircraft)
aircraftDecorator.ordinary() // 發(fā)射普通子彈
aircraftDecorator.laser() // 發(fā)射激光
aircraftDecorator.guidedMissile() // 發(fā)射導(dǎo)彈
// 可以看到在不更改源代碼的情況下對它進(jìn)行了裝飾擴(kuò)展
7.代理模式
- 代理模式的關(guān)鍵是,當(dāng)客戶不方便直接訪問一個對象或者不滿足需要的時候,提供一個替身對象來控制對這個對象的訪問,客戶實際上訪問的是替身對象。替身對象對請求做出一些處理之后,再把請求轉(zhuǎn)交給本體對象。
- 代理和本體接口需要一致性,代理和本體之間可以說是鴨子類型的關(guān)系,不在乎他怎么實現(xiàn)的,只要它們之間暴露的方法一致既可。
優(yōu)點
- 職責(zé)清晰,高擴(kuò)展性,智能化
缺點
- 當(dāng)對象和對象之間增加了代理可能會影響到處理的速度。
- 實現(xiàn)代理需要額外的工作,有些代理會非常的復(fù)雜。
例子
- 我們都知道,領(lǐng)導(dǎo)擁有公司的最高權(quán)限,假設(shè)公司有員工100個,如果每個人都去找領(lǐng)導(dǎo)去處理事務(wù),那領(lǐng)導(dǎo)肯定會崩潰,因此領(lǐng)導(dǎo)招聘了一個秘書幫他收集整理事務(wù),秘書會在合適時間一次性將需要處理的業(yè)務(wù)交給老板處理,在這里秘書就是領(lǐng)導(dǎo)的一個代理角色。
// 員工
class Staff {
constructor(affairType){
this.affairType = affairType
}
applyFor(target){
target.receiveApplyFor(this.affairType)
}
}
// 秘書
class Secretary {
constructor(){
this.leader = new Leader()
}
receiveApplyFor(affair){
this.leader.receiveApplyFor(affair)
}
}
//領(lǐng)導(dǎo)
class Leader {
receiveApplyFor(affair){
console.log(`批準(zhǔn):${affair}`)
}
}
const staff = new Staff('升職加薪')
staff.applyFor(new Secretary()) // 批準(zhǔn):升職加薪
8.外觀模式
- 外觀模式本質(zhì)就是封裝交互,隱藏系統(tǒng)的復(fù)雜性,提供一個可以訪問的接口。由一個將子系統(tǒng)一組的接口集成在一起的高層接口,以提供一個一致的外觀,減少外界與多個子系統(tǒng)之間的直接交互,從而更方便的使用子系統(tǒng)。
優(yōu)點
- 減少系統(tǒng)的相互依賴,以及安全性和靈活性
缺點
- 違反開放封閉原則,有變動的時候更改會非常麻煩,即使繼承重構(gòu)都不可行。
例子
- 外觀模式經(jīng)常被用于處理高級游覽器的和低版本游覽器的一些接口的兼容處理
function addEvent(el,type,fn){
if(el.addEventlistener){// 高級游覽器添加事件DOM API
el.addEventlistener(type,fn,false)
}else if(el.attachEvent){// 低版本游覽器的添加事件API
el.attachEvent(`on${type}`,fn)
}else {//其他
el[type] = fn
}
}
- 另一種場景,在某個函數(shù)中的某個參數(shù)可傳可不傳的情況下,通過函數(shù)重載的方式,讓傳參更靈活。
function bindEvent(el,type,selector,fn){
if(!fn){
fn = selector
}
// 其他代碼
console.log(el,type,fn)
}
bindEvent(document.body,'click','#root',()=>{})
bindEvent(document.body,'click',()=>{})
9.發(fā)布訂閱模式
- 發(fā)布訂閱又稱觀察者模式,它定義對象之間的1對N的依賴關(guān)系,當(dāng)其中一個對象發(fā)生變化時,所有依賴于它的對象都會得到通知。
- 發(fā)布訂閱模式經(jīng)常出現(xiàn)在我們的工作場景中,如:當(dāng)你給DOM綁定一個事件就已經(jīng)使用了發(fā)布訂閱模式,通過訂閱DOM上的click事件,當(dāng)被點擊時會向訂閱者發(fā)布消息。
優(yōu)點
- 觀察者和被觀察者它們之間是抽象耦合的。并且建立了觸發(fā)機(jī)制。
缺點
- 當(dāng)訂閱者比較多的時候,同時通知所有的訂閱者可能會造成性能問題。
- 在訂閱者和訂閱目標(biāo)之間如果循環(huán)引用執(zhí)行,會導(dǎo)致崩潰。
- 發(fā)布訂閱模式?jīng)]有辦法提供給訂閱者所訂閱的目標(biāo)它是怎么變化的,僅僅只知道它變化了。
例子
- 比喻前段時間的冬奧會,項目還沒有開始的時候可以提前預(yù)定,等到項目快開始的時,APP會提前給我們發(fā)送通知即將開始的項目,而沒到時間的不通知,另外在項目還沒有開始的時候,可以取消訂閱避免接受到通知。根據(jù)這個需求我們來寫一個例子吧
class Subject {
constructor(){
this.observers = {}
this.key = ''
}
add(observer){
const key = observer.project
if (!this.observers[key]) {
this.observers[key] = []
}
this.observers[key].push(observer)
}
remove(observer){
const _observers = this.observers[observer.project]
console.log(_observers,11)
if(_observers.length){
_observers.forEach((item,index)=>{
if(item === observer){
_observers.splice(index,1)
}
})
}
}
setObserver(subject){
this.key = subject
this.notifyAllObservers()
}
notifyAllObservers(){
this.observers[this.key].forEach((item,index)=>{
item.update()
})
}
}
class Observer {
constructor(project,name) {
this.project = project
this.name = name
}
update() {
console.log(`尊敬的:${this.name} 你預(yù)約的項目:【${this.project}】 馬上開始了`)
}
}
const subject = new Subject()
const xiaoming = new Observer('滑雪','xiaoming')
const A = new Observer('大跳臺','A')
const B = new Observer('大跳臺','B')
const C = new Observer('大跳臺','C')
subject.add(xiaoming)
subject.add(A)
subject.add(B)
subject.add(C)
subject.remove(B) // 取消訂閱
subject.setObserver('大跳臺')
/** 執(zhí)行結(jié)果
* 尊敬的:A 你預(yù)約的項目:【大跳臺】 馬上開始了
* 尊敬的:C 你預(yù)約的項目:【大跳臺】 馬上開始了
*/
10.迭代器模式
- 迭代器模式是指提供一種方法順序訪問一個聚合對象中的每個元素,并且不需要暴露該對象的內(nèi)部。
優(yōu)點
- 它支持以不同的方式遍歷一個聚合對象。
- 迭代器簡化了聚合類。在同一個聚合上可以有多個遍歷。
- 在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
缺點
- 由于迭代器模式將存儲數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對應(yīng)增加新的迭代器類,類的個數(shù)成對增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。
例子
迭代器分為內(nèi)部迭代器和外部迭代器,它們有各自的適用場景。
- 內(nèi)部迭代器
// 內(nèi)部迭代器表示內(nèi)部已經(jīng)定義好了迭代規(guī)則,它完全接受整個迭代過程,外部只需一次初始調(diào)用。
Array.prototype.MyEach = function(fn){
for(let i = 0;i<this.length;i++){
fn(this[i],i,this)
}
}
Array.prototype.MyEach = function(fn){
for(let i = 0;i<this.length;i++){
fn(this[i],i,this)
}
}
[1,2,3,4].MyEach((item,index)=>{
console.log(item,index)
})
- 外部迭代器
// 外部迭代器必須顯示的迭代下一個元素。它增加了調(diào)用的復(fù)雜度,但也增加了迭代器的靈活性,可以手動控制迭代的過程。
class Iterator{
constructor(arr){
this.current = 0
this.length = arr.length
this.arr = arr
}
next(){
return this.getCurrItem()
}
isDone(){
return this.current>=this.length
}
getCurrItem(){
return {
done:this.isDone(),
value:this.arr[this.current++]
}
}
}
let iterator =new Iterator([1,2,3])
while(!(item=iterator.next()).done) {
console.log(item)
}
iterator.next()
/* 下面的數(shù)據(jù)格式是不是有點熟悉
{done: false, value: 1}
{done: false, value: 2}
{done: false, value: 3}
{done: true, value: undefined}
*/
11.狀態(tài)模式
- 允許一個對象在其內(nèi)部狀態(tài)改變的時候改變其行為,對象看起來似乎修改了它的類,通俗一點的將就是記錄一組狀態(tài),每個狀態(tài)對應(yīng)一個實現(xiàn),實現(xiàn)的時候根據(jù)狀態(tài)去運行實現(xiàn)。
優(yōu)點
- 將所有與某個狀態(tài)有關(guān)的行為放到一個類中,并且可以方便地增加新的狀態(tài),只需要改變對象狀態(tài)即可改變對象的行為。
- 允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對象合成一體,而不是某一個巨大的條件語句塊。
- 可以讓多個環(huán)境對象共享一個狀態(tài)對象,從而減少系統(tǒng)中對象的個數(shù)。
缺點
- 狀態(tài)模式的使用必然會增加系統(tǒng)類和對象的個數(shù)。
- 狀態(tài)模式的結(jié)構(gòu)與實現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。
- 狀態(tài)模式對"開閉原則"的支持并不太好,對切換狀態(tài)的狀態(tài)模式增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無法切換到新增狀態(tài),而且修改某個狀態(tài)類的行為也需修改對應(yīng)類的源代碼。
例子
- lol中的瑞文的Q有三段攻擊,同一個按鍵,在不同的狀態(tài)下,攻擊的行為不同。通常情況下,我們通過if...else也可以實現(xiàn),但是這樣明顯不利于擴(kuò)展,違反了開放封閉原則。接下來用代碼來描述這種場景。
class State {
constructor(attack){
this.attack = attack
}
handle(context){
console.log(this.attack)
context.setState(this)
}
}
class Context {
constructor(){
this.state = null
}
getState(){
return this.state
}
setState(state){
this.state = state
}
}
const q1 = new State('q1 第1擊'),
q2 = new State('q2 第2擊'),
q3 = new State('q3 第3擊'),
context = new Context()
q1.handle(context)//q1 第1擊
q2.handle(context)//q2 第2擊
q3.handle(context)//q3 第3擊
12.策略模式
- 策略模式指的是定義一系列算法,把他們一個個封裝起來,目的就是將算法的使用和算法的實現(xiàn)分離開來。同時它還可以用來封裝一系列的規(guī)則,比如常見的表單驗證規(guī)則,只要這些規(guī)則指向的目標(biāo)一致,并且可以被替換使用,那么就可以用策略模式來封裝它們。
優(yōu)點
- 算法可以自由切換,避免了使用多層條件判斷,增加了擴(kuò)展性
缺點
- 策略類增多,所有策略類都需要對外暴露。
例子
- 剛?cè)脒@個行業(yè)的時候,寫表單驗證經(jīng)常無止境的if...else寫法,意識到這種寫法不靠譜,于是我把檢驗規(guī)則放在一個對象中,在函數(shù)中對它進(jìn)行控制,把規(guī)則與實現(xiàn)進(jìn)行了分離,每次只需要在封裝的規(guī)則中去修改配置。在后面的多種場景都用這種方法,解決了頻繁使用if...else的問題,當(dāng)?shù)谝淮谓佑|倒策略模式才知道這種寫法也算策略模式。
const rules = {
cover_img: {
must: false,
msg: '請上傳封面圖片',
val: ''
},
name: {
must: true,
msg: '姓名不能為空',
val: ''
},
sex: {
must: true,
msg: '請?zhí)顚懶詣e',
val: ''
},
birthday: {
must: false,
msg: '請選擇生日',
val: ''
},
}
function verify(){
for(const key in rules){
if(rules[key].must&&!rules[key].val){
console.log(rules[key].msg)
}
}
}
verify()
// 姓名不能為空
// 請?zhí)顚懶詣e
- 上面的例子是以js方式寫的,在javascript將函數(shù)作為一等公民的語言里,策略模式就是隱形的,它已經(jīng)融入到了javascript的語言中,所以以javascript方式的策略模式會顯得簡單直接。不過我們依然要了解傳統(tǒng)的策略模式,下面來看看傳統(tǒng)的策略模式的例子。
//html-----------------
<form action="http:// xxx.com/register" id="registerForm" method="post">
請輸入用戶名:<input type="text" name="userName" />
請輸入密碼:<input type="text" name="password" />
請輸入手機(jī)號碼:<input type="text" name="phoneNumber" />
<button>提交</button>
</form>
// js------------------
class Strategies {
constructor() {
this.rules = {}
}
add(key, rule) {
this.rules[key] = rule
return this
}
}
class Validator {
constructor(strategies) {
this.cache = [] // 保存檢驗規(guī)則
this.strategies = strategies
}
add(dom, rules) {
rules.forEach((rule) => {
const strategyAry = rule.strategy.split(':')
this.cache.push(() => {
const strategy = strategyAry.shift()
strategyAry.unshift(dom.value)
strategyAry.push(rule.errorMsg)
console.log(this.strategies[strategy])
return this.strategies[strategy].apply(dom, strategyAry)
})
});
}
start() {
for (let i = 0,validatorFunc; validatorFunc =this.cache[i++]; ) {
const msg = validatorFunc()
if (msg) {
return msg
}
}
}
}
const registerForm = document.getElementById('registerForm') // 獲取formDom節(jié)點
const strategies = new Strategies()
strategies.add('isNonEmpty', function(value, errorMsg) {
if (!value) {
return errorMsg
}
}).add('minLength', function(value, length, errorMsg) {
if (value.length < length) {
return errorMsg
}
}).add('isMobile', function(value, errorMsg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
return errorMsg
}
})
function validataFunc() {
const validator = new Validator(strategies.rules)
// 多個校驗規(guī)則
validator.add(registerForm.userName, [
{
strategy: 'isNonEmpty',
errorMsg: '用戶名不能為空'
}, {
strategy: 'minLength:10',
errorMsg: '用戶名長度不能少于10位'
}
])
validator.add(registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密碼長度不能少于6位'
}])
validator.add(registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手機(jī)號碼格式不對'
}])
const errorMsg = validator.start()
return errorMsg // 返回錯誤信息。
}
registerForm.onsubmit = function () {
const errorMsg = validataFunc()
if (errorMsg) { // 如果存在錯誤信息,顯示錯誤信息,并且阻止onsubmit默認(rèn)事件
console.log(errorMsg)
return false
}
}
13.命令模式
- 命令模式中的命令指的是一個執(zhí)行某些特定的事情的指令。
- 命令模式最常見的應(yīng)用場景如:有時候需要向某些對象發(fā)送請求,但是并不知道請求的接收者是誰,也不知道被請求的操作是什么。此時可以通過一種松耦合的方式來設(shè)計程序,使得請求發(fā)送者和請求接收者消除彼此之間的耦合關(guān)系。
優(yōu)點
- 降低了代碼的耦合度,易擴(kuò)展,出現(xiàn)新的命令可以很容易的添加進(jìn)去
缺點
- 命令模式使用過度會導(dǎo)致代碼中存在過多的具體命令。
例子
- 假設(shè)在一個項目中開發(fā)某個頁面,其中某個程序員負(fù)責(zé)繪制靜態(tài)頁面,包括某些按鈕,而另一個程序員負(fù)責(zé)開發(fā)這幾個按鈕的具體行為。負(fù)責(zé)靜態(tài)頁面的程序員暫時不知道這些按鈕未來會發(fā)生什么,在不知道具體行為是什么作什么的情況下,通過命令模式的幫助,解開按鈕和負(fù)責(zé)具體行為對象之間的耦合。
// html-------------------
<button id="button2">點擊按鈕 1</button>
<button id="button2">點擊按鈕 2</button>
<button id="button3">點擊按鈕 3</button>
// js---------------------
const button1 = document.getElementById('button1'),
button2 = document.getElementById('button2'),
button3 = document.getElementById('button3');
const MenBar = {
refresh:function(){
console.log('刷新菜單目錄')
}
}
const SubMenu = {
add:function(){
console.log('增加子菜單')
},
del:function(){
console.log('刪除子菜單')
}
}
function setCommand(el,command){
el.onclick = function(){
command.execute()
}
}
class MenuBarCommand{
constructor(receiver,key){
this.receiver = receiver
this.key = key
}
execute(){
this.receiver[this.key]()
}
}
setCommand(button1,new MenuBarCommand(MenBar,'refresh'))
setCommand(button2,new MenuBarCommand(SubMenu,'add'))
setCommand(button3,new MenuBarCommand(SubMenu,'del'))
14.組合模式
- 組合模式就是由一些小的子對象構(gòu)建出的更大的對象,而這些小的子對象本身可能也是由多個孫對象組合而成的。
- 組合模式將對象組合成樹狀結(jié)構(gòu),以表示“部分-整體”的層次結(jié)構(gòu)。除了用來表示樹狀結(jié)構(gòu)之外,組合模式的另一個好處就是通過對象的多態(tài)性表現(xiàn),使得用戶對單個對象和組合對象的使用具有一致性。
優(yōu)點
- 高層模塊調(diào)用簡單,節(jié)點可以自由添加
缺點
- 其葉對象和子對象聲明都是實現(xiàn)類,而不是接口,這違反了依賴倒置原則
例子
- 以我們最常見的文件夾和文件的關(guān)系,非常適合用組合模式來描述,文件夾可以包括子文件夾和文件,文件不能包括任何文件,這種關(guān)系讓最終會形成一棵樹。下面來實現(xiàn)文件的添加,掃描該文件里的文件,并且可以刪除文件。
// 文件夾類
class Folder {
constructor(name) {
this.name = name
this.parent = null;
this.files = []
}
// 添加文件
add(file) {
file.parent = this
this.files.push(file)
return this
}
// 掃描文件
scan() {
console.log(`開始掃描文件夾:${this.name}`)
this.files.forEach(file => {
file.scan()
});
}
// 刪除指定文件
remove() {
if (!this.parent) {
return
}
for (let files = this.parent.files, i = files.length - 1; i >= 0; i--) {
const file = files[i]
if (file === this) {
files.splice(i, 1)
break
}
}
}
}
// 文件類
class File {
constructor(name) {
this.name = name
this.parent = null
}
add() {
throw new Error('文件下面不能添加任何文件')
}
scan() {
console.log(`開始掃描文件:${this.name}`)
}
remove() {
if (!this.parent) {
return
}
for (let files = this.parent.files, i = files.length - 1; i >= 0; i++) {
const file = files[i]
if (file === this) {
files.splice(i, 1)
}
}
}
}
const book = new Folder('電子書')
const js = new Folder('js')
const node = new Folder('node')
const vue = new Folder('vue')
const js_file1 = new File('javascript高級程序設(shè)計')
const js_file2 = new File('javascript忍者秘籍')
const node_file1 = new File('nodejs深入淺出')
const vue_file1 = new File('vue深入淺出')
const designMode = new File('javascript設(shè)計模式實戰(zhàn)')
js.add(js_file1).add(js_file2)
node.add(node_file1)
vue.add(vue_file1)
book.add(js).add(node).add(vue).add(designMode)
book.remove()
book.scan()
15.模塊方法模式
- 模塊方法模式是一種基于繼承的設(shè)計模式,在javascript中沒有真正意義上的繼承,所有繼承都來自原型(prototype)上的繼承,隨著ES6的class到來,實現(xiàn)了繼承的“概念”,讓我們可以以一種很方便簡潔的方式繼承,但其本質(zhì)上還是原型繼承。
- 模板方法模式由兩部分組成,第一部分是抽象父類,第二部分是具體的實現(xiàn)子類。抽象父類主要封裝了子類的算法框架,以及實現(xiàn)了一些公共的方法和其他方法的執(zhí)行順序。子類通過繼承父類,繼承了父類的算法框架,并進(jìn)行重寫。
優(yōu)點
- 提供公共的代碼便于維護(hù)。行為由父類控制,具體由子類來實現(xiàn)。
缺點
- 其每一個具體實現(xiàn)都需要繼承的子類來實現(xiàn),這無疑導(dǎo)致類的個數(shù)增加,使得系統(tǒng)龐大。
例子
- 拿咖啡和茶的例子來說,制作咖啡和茶都需要燒開水,把水煮沸是一個公共方法,隨后的怎么沖泡,把什么倒進(jìn)杯子,以及添加什么配料,它們可能各不一樣,根據(jù)以上特點,開始我們的例子。
// 抽象父類
class Beverage {
boilWater(){
console.log('把水煮沸')
}
brew(){
throw new Error('字類必須重寫brew方法')
}
pourInCup(){
throw new Error('字類必須重寫pourInCup方法')
}
addCondiments(){
throw new Error('字類必須重寫addCondiments方法')
}
init(){
this.boilWater()
this.brew()
this.pourInCup()
this.addCondiments()
}
}
// 咖啡類
class Coffee extends Beverage {
brew(){
console.log('用沸水沖泡咖啡')
}
pourInCup(){
console.log('把咖啡倒進(jìn)杯子')
}
addCondiments(){
console.log('加糖和牛奶')
}
}
// 茶類
class Tea extends Beverage {
brew(){
console.log('用沸水侵泡茶葉')
}
pourInCup(){
console.log('把茶倒進(jìn)杯子')
}
addCondiments(){
console.log('加檸檬')
}
}
const coffee = new Coffee()
coffee.init()
const tea = new Tea()
tea.init()
16.享元模式
- 享元模式是一種用于性能優(yōu)化的模式,核心是運用共享技術(shù)來有效支持大量的細(xì)粒度對象。如果系統(tǒng)中創(chuàng)建了大量的類似對象,會導(dǎo)致內(nèi)存消耗過高,通過享用模式處理重用類似對象,減少內(nèi)存消耗的問題,達(dá)到性能優(yōu)化方案。
- 享元模式的關(guān)鍵是如何區(qū)分內(nèi)部狀態(tài)和外部狀態(tài)
- 內(nèi)部狀態(tài):可以被對象共享,通常不會改變的稱為內(nèi)部狀態(tài)
- 外部狀態(tài):取決于具體的場景,根據(jù)具體的場景變化,并且不能被共享的稱為外部狀態(tài)
優(yōu)點
- 減少了大批量對象的創(chuàng)建,降低了系統(tǒng)了內(nèi)存。
缺點
- 提高了系統(tǒng)的復(fù)雜度,需要分離出外部狀態(tài)和內(nèi)部狀態(tài),而且外部狀態(tài)具有固有化的性質(zhì),不應(yīng)該隨著內(nèi)部狀態(tài)的變化而變化,否則會造成系統(tǒng)的混亂。
例子
let id = 0
// 定義內(nèi)部狀態(tài)
class Upload {
constructor(uploadType) {
this.uploadType = uploadType
}
// 點擊刪除時 小于3000直接刪除,大于3000通過confirm提示彈窗刪除。
delFile(id) {
uploadManager.setExternalState(id,this)
if(this.fileSize < 3000){
return this.dom.parentNode.removeChild(this.dom)
}
if(window.confirm(`確定要刪除該文件嗎?${this.fileName}`)){
return this.dom.parentNode.removeChild(this.dom)
}
}
}
// 外部狀態(tài)
class uploadManager {
static uploadDatabase = {}
static add(id, uploadType, fileName, fileSize) {
const filWeightObj = UploadFactory.create(uploadType)
const dom = this.createDom(fileName, fileSize, () => {
filWeightObj.delFile(id)
})
this.uploadDatabase[id] = {
fileName,
fileSize,
dom
}
}
// 創(chuàng)建DOM 并且為button綁定刪除事件。
static createDom(fileName, fileSize, fn) {
const dom = document.createElement('div')
dom.innerHTML = `
<span>文件名稱:${fileName},文件大?。?{fileSize}</span>
<button class="delFile">刪除</button>
`
dom.querySelector('.delFile').onclick = fn
document.body.append(dom)
return dom
}
static setExternalState(id, flyWeightObj) {
const uploadData = this.uploadDatabase[id]
for (const key in uploadData) {
if (Object.hasOwnProperty.call(uploadData, key)) {
flyWeightObj[key] = uploadData[key]
}
}
}
}
// 定義一個工廠創(chuàng)建upload對象,如果其內(nèi)部狀態(tài)實例對象存在直接返回,反之創(chuàng)建保存并返回。
class UploadFactory {
static createFlyWeightObjs = {}
static create(uploadType) {
if (this.createFlyWeightObjs[uploadType]) {
return this.createFlyWeightObjs[uploadType]
}
return this.createFlyWeightObjs[uploadType] = new Upload(uploadType)
}
}
// 開始加載
const startUpload = (uploadType, files)=>{
for (let i = 0, file; file = files[i++];) {
uploadManager.add(++id, uploadType, file.fileName, file.fileSize)
}
}
startUpload('plugin', [
{fileName: '1.txt',fileSize: 1000},
{fileName: '2.html',fileSize: 3000},
{fileName: '3.txt',fileSize: 5000}
]);
startUpload('flash', [
{fileName: '4.txt',fileSize: 1000},
{fileName: '5.html',fileSize: 3000},
{fileName: '6.txt',fileSize: 5000}
]);
17.職責(zé)鏈模式
- 職責(zé)鏈模式的定義是:使用多個對象都有機(jī)會處理請求,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系,將這些對象鏈成一條鏈,并沿著這條鏈傳遞該請求,知道有一個對象處理它為止。
優(yōu)點
- 降低耦合度,它將請求的發(fā)送者和接收者解耦。
- 簡化了對象,使得對象不需要知道鏈的結(jié)構(gòu)。
- 增強(qiáng)給對象指派職責(zé)的靈活性。通過改變鏈內(nèi)的成員或者調(diào)動它們的次序,允許動態(tài)地新增或者刪除責(zé)任。
缺點
- 不能保證每一條請求都一定被接收。
- 系統(tǒng)性能將受到一定影響,而且在進(jìn)行代碼調(diào)試時不太方便,可能會造成循環(huán)調(diào)用。
- 可能不容易觀察運行時的特征,有礙于排除問題。
例子
- 假設(shè)我們負(fù)責(zé)一個手機(jī)售賣的電商網(wǎng)站,分別繳納500元和200元定金的兩輪預(yù)訂后,會分別收到100元和50元的優(yōu)惠券,而沒有支付定金的則視為普通購買,沒有優(yōu)惠券,并且在庫存有限的情況下也無法保證能購買到。
class Order500 {
constructor(){
this.orderType = 1
}
handle(orderType, pay, stock){
if(orderType === this.orderType&&pay){
console.log('500元定金預(yù)約,得到100元優(yōu)惠券')
}else {
return 'nextSuccessor'
}
}
}
class Order200 {
constructor(){
this.orderType = 2
}
handle(orderType, pay, stock){
if(orderType === this.orderType&&pay){
console.log('200元訂金預(yù)約,得到50元優(yōu)惠卷')
}else {
return 'nextSuccessor'
}
}
}
class OrderNormal {
constructor(){
this.stock = 0
}
handle(orderType, pay, stock){
if (stock > this.stock) {
console.log('普通購買,無優(yōu)惠卷')
} else {
console.log('手機(jī)庫存不足')
}
}
}
class Chain {
constructor(order){
this.order = order
this.successor = null
}
setNextSuccessor(successor){
return this.successor = successor
}
passRequest(...val){
const ret = this.order.handle.apply(this.order,val)
if(ret === 'nextSuccessor'){
return this.successor&&this.successor.passRequest.apply(this.successor,val)
}
return ret
}
}
console.log(new Order500())
var chainOrder500 = new Chain( new Order500() );
var chainOrder200 = new Chain( new Order200() );
var chainOrderNormal = new Chain( new OrderNormal() );
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );
chainOrder500.passRequest( 1, true, 500 ); // 輸出:500 元定金預(yù)購,得到 100 優(yōu)惠券
chainOrder500.passRequest( 2, true, 500 ); // 輸出:200 元定金預(yù)購,得到 50 優(yōu)惠券
chainOrder500.passRequest( 3, true, 500 ); // 輸出:普通購買,無優(yōu)惠券
chainOrder500.passRequest( 1, false, 0 ); // 輸出:手機(jī)庫存不足
18.中介模式
- 中介者模式的作用就是解除對象與對象之間的緊密耦合關(guān)系。增加一個中介者對象之后,所有相關(guān)對象都通過中介者對象來通信,而不是相互引用,所以當(dāng)一個對象發(fā)生改變時,只需要通過中介者對象即可。中介者使各對象之間耦合松散,而且可以獨立改變他們之間的交互。中介者模式使網(wǎng)狀的多對多關(guān)系變成了相對簡單的一對多關(guān)系。
優(yōu)點
- 降低了類的復(fù)雜度,將一對多轉(zhuǎn)化成了一對一。各個類之間的解耦。
缺點
- 當(dāng)中介者變得龐大復(fù)雜,導(dǎo)致難以維護(hù)。
例子
// html-----------
選擇顏色:<select name="" id="colorSelect">
<option value="">請選擇</option>
<option value="red">紅色</option>
<option value="blue">藍(lán)色</option>
</select>
<br />
選擇內(nèi)存:<select name="" id="memorySelect">
<option value="">請選擇</option>
<option value="32G">32G</option>
<option value="63G">64G</option>
</select>
<br />
輸入購買數(shù)量:<input type="text" id="numberInput" />
<br />
<div>你選擇了顏色:<span id="colorInfo"></span></div>
<div>你選擇了內(nèi)存:<span id="memoryInfo"></span></div>
<div>你選擇了數(shù)量:<span id="numberInfo"></span></div>
<button id="nextBtn" disabled="true">請選擇手機(jī)顏色和購買數(shù)量</button>
// js -------------------
const goods = {
"red|32G": 3,
"red|16G": 0,
"blue|32G": 1,
"blue|16G": 6
},
colorSelect = document.getElementById('colorSelect'),
memorySelect = document.getElementById('memorySelect'),
numberInput = document.getElementById('numberInput'),
colorInfo = document.getElementById('colorInfo'),
memoryInfo = document.getElementById('memoryInfo'),
numberInfo = document.getElementById('numberInfo'),
nextBtn = document.getElementById('nextBtn'),
mediator = (function () {
return {
changed(obj) {
const color = colorSelect.value,
memory = memorySelect.value,
number = numberInput.value,
stock = goods[`${color}|${memory}`]
if (obj === colorSelect) {
colorInfo.innerHTML = color
} else if (obj === memorySelect) {
memoryInfo.innerHTML = memory
} else if (obj === numberInput) {
numberInfo.innerHTML = number
}
if (!color) {
nextBtn.disabled = true
nextBtn.innerHTML = '請選擇手機(jī)顏色'
return
}
if (!memory) {
nextBtn.disabled = true
nextBtn.innerHTML = '請選擇內(nèi)存大小'
return
}
if (Number.isInteger(number - 0) && number < 1) {
nextBtn.disabled = true
nextBtn.innerHTML = '請輸入正確的購買數(shù)量'
return
}
nextBtn.disabled = false
nextBtn.innerHTML = '放入購物車'
}
}
})()
colorSelect.onchange = function () {
mediator.changed(this)
}
memorySelect.onchange = function () {
mediator.changed(this)
}
numberInput.oninput = function () {
mediator.changed(this)
}
19.原型模式
- 原型模式是指原型實例指向創(chuàng)建對象的種類,通過拷貝這些原型來創(chuàng)建新的對象,說白了就是克隆自己,生成一個新的對象。
優(yōu)點
- 不再依賴構(gòu)造函數(shù)或者類創(chuàng)建對象,可以將這個對象作為一個模板生成更多的新對象。
缺點
- 對于包含引用類型值的屬性來說,所有實例在默認(rèn)的情況下都會取得相同的屬性值。
例子
const user = {
name:'小明',
age:'30',
getInfo(){
console.log(`姓名:${this.name},年齡:${this.age}`)
}
}
const xiaozhang = Object.create(user)
xiaozhang.name = '小張'
xiaozhang.age = 18
xiaozhang.getInfo() // 姓名:小張,年齡:18
user.getInfo() // 姓名:小明,年齡:30
20.備忘錄模式
- 備忘錄模式就是在不破壞封裝的前提下,捕獲一個對象內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài),以保證以后可以將對象恢復(fù)到原先的狀態(tài)。
優(yōu)點
- 給用戶提供了一種可以恢復(fù)狀態(tài)的機(jī)制,可以使用戶能夠比較方便地回到某個歷史的狀態(tài)。
- 實現(xiàn)了信息的封裝,使得用戶不需要關(guān)心狀態(tài)的保存細(xì)節(jié)。
缺點
- 如果類的成員變量過多,勢必會占用比較大的資源,而且每一次保存都會消耗一定的內(nèi)存。
例子
// 棋子
class ChessPieces {
constructor(){
this.chess = {}
}
// 獲取棋子
getChess(){
return this.chess
}
}
// 記錄棋路
class Record {
constructor(){
this.chessTallyBook = [] // 記錄棋路
}
recordTallyBook(chess){
// console.log(this.chessTallyBook.includes(chess))
const isLoadtion = this.chessTallyBook.some(
item=>item.location === chess.location
)
if(isLoadtion){
console.log(`${chess.type},${chess.location}已存在其他棋子`)
}else {
this.chessTallyBook.push(chess)
}
// this.chessTallyBook.some(item=>item.location === chess.location)
}
getTallyBook(){
return this.chessTallyBook.pop()
}
}
// 下棋規(guī)則
class ChessRule {
constructor(){
this.chessInfo = {}
}
playChess(chess){
this.chessInfo = chess
}
getChess(){
return this.chessInfo
}
// 記錄棋路
recordTallyBook(){
return new ChessPieces(this.chessInfo)
}
// 悔棋
repentanceChess(chess){
this.chessInfo = chess.getTallyBook()
}
}
const chessRule = new ChessRule()
const record = new Record()
chessRule.playChess({
type:'黑棋',
location:'X10,Y10'
})
record.recordTallyBook(chessRule.getChess())//記錄棋路
chessRule.playChess({
type:'白棋',
location:'X11,Y10'
})
record.recordTallyBook(chessRule.getChess())//記錄棋路
chessRule.playChess({
type:'黑棋',
location:'X11,Y11'
})
record.recordTallyBook(chessRule.getChess())//記錄棋路
chessRule.playChess({
type:'白棋',
location:'X12,Y10'
})
console.log(chessRule.getChess())//{type:'白棋',location:'X12,Y10'}
chessRule.repentanceChess(record) // 悔棋
console.log(chessRule.getChess())//{type:'黑棋',location:'X11,Y11'}
chessRule.repentanceChess(record) // 悔棋
console.log(chessRule.getChess())//{type:'白棋',location:'X11,Y10'}
21.橋接模式
- 橋接模式是指將抽象部分與它的實現(xiàn)部分分離,使它們各自獨立的變化,通過使用組合關(guān)系代替繼承關(guān)系,降低抽象和實現(xiàn)兩個可變維度的耦合度。
優(yōu)點
- 抽象和實現(xiàn)的分離。優(yōu)秀的擴(kuò)展能力。實現(xiàn)細(xì)節(jié)對客戶透明。
缺點
- 橋接模式的引入會增加系統(tǒng)的理解與設(shè)計難度,由于聚合關(guān)聯(lián)關(guān)系建立在抽象層,要求開發(fā)者針對抽象進(jìn)行設(shè)計與編程。
例子
- 比如我們所用的手機(jī),蘋果的iphoneX,和華為的mate40,品牌和型號就是它們共同的抽象部分,可以把他們單獨提取出來。
class Phone {
constructor(brand,modle){
this.brand = brand
this.modle = modle
}
showPhone(){
return `手機(jī)的品牌:${this.brand.getBrand()},型號${this.modle.getModle()}`
}
}
class Brand {
constructor(brandName){
this.brandName = brandName
}
getBrand(){
return this.brandName
}
}
class Modle {
constructor(modleName){
this.modleName = modleName
}
getModle(){
return this.modleName
}
}
const phone = new Phone(new Brand('華為'),new Modle('mate 40'))
console.log(phone.showPhone())
22.訪問者模式
- 訪問者模式是將數(shù)據(jù)的操作和數(shù)據(jù)的結(jié)構(gòu)進(jìn)行分離,對數(shù)據(jù)中各元素的操作封裝獨立的類,使其在不改變數(shù)據(jù)結(jié)構(gòu)情況下擴(kuò)展新的數(shù)據(jù)。
優(yōu)點
- 符合單一職責(zé)原則。具有優(yōu)秀的擴(kuò)展性和靈活性。
缺點
- 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。
例子
class Phone {
accept() {
throw new Error('子類的accept必須被重寫')
}
}
class Mata40Pro extends Phone {
accept() {
const phoneVisitor = new PhoneVisitor()
return phoneVisitor.visit(this)
}
}
class IPhone13 extends Phone {
accept() {
const phoneVisitor = new PhoneVisitor()
return phoneVisitor.visit(this)
}
}
// 訪問者類
class PhoneVisitor {
visit(phone) {
if (phone.constructor === IPhone13) {
return {
os: 'ios',
chip: 'A15仿生芯片',
screen: '電容屏'
}
} else if (phone.constructor === Mata40Pro) {
return {
os: 'HarmonyOS',
chip: 'Kirin 9000',
GPUType: 'Mali-G78',
port: 'type-c'
}
}
}
}
const mata40Pro = new Mata40Pro()
console.log(mata40Pro.accept())
23.解釋器模式
- 解釋器模式提供了評估語言的語法或表達(dá)式的方式,它屬于行為型模式。這種模式實現(xiàn)了一個表達(dá)式接口該接口,該接口解釋一個特定的上下文。
優(yōu)點
- 可擴(kuò)展性比較好,靈活。增加了新的解釋表達(dá)式的方式。
缺點
- 可利用場景比較少,在web開發(fā)中幾乎不可見。對于復(fù)雜的環(huán)境比較難維護(hù)。
- 解釋器模式會引起類膨脹。它還采用遞歸調(diào)用方法,沒控制好可能會導(dǎo)致崩潰。
例子
class TerminalExpression {
constructor(data) {
this.data = data
}
interpret(context) {
if (context.indexOf(this.data) > -1) {
return true;
}
return false;
}
}
class OrExpression {
constructor(expr1, expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
interpret(context) {
return this.expr1.interpret(context) || this.expr2.interpret(context);
}
}
class AndExpression {
constructor(expr1, expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
interpret(context) {
return this.expr1.interpret(context) && this.expr2.interpret(context);
}
}
class InterpreterPatternDemo {
static getMaleExpression() {
const robert = new TerminalExpression("小明");
const john = new TerminalExpression("小龍");
return new OrExpression(robert, john);
}
static getMarriedWomanExpression() {
const julie = new TerminalExpression("張三");
const married = new TerminalExpression("小紅");
return new AndExpression(julie, married);
}
static init(args) {
const isMale = this.getMaleExpression();
const isMarriedWoman = this.getMarriedWomanExpression();
console.log(`小龍是男性?${isMale.interpret("小龍")}`)
console.log(`小紅是一個已婚婦女?${isMarriedWoman.interpret("小紅 張三")}`)
}
}
InterpreterPatternDemo.init()
總結(jié)
- 以上是我歷時將近一個月的學(xué)習(xí)總結(jié),然而一個月的時間是遠(yuǎn)遠(yuǎn)不夠的,在寫完這篇文章后,依舊對某些設(shè)計模式的應(yīng)用場景缺乏了解。設(shè)計模式是要長時間深入研究的知識點,需要結(jié)合實際的場景去練習(xí)模仿,不斷的去思考。另外由于js的特性,很多設(shè)計模式在js中是殘缺的,非完全體的,強(qiáng)行用js去模仿傳統(tǒng)的設(shè)計模式顯的有點雞肋。但是隨著typescript的出現(xiàn),設(shè)計模式在ts中可以無限接近傳統(tǒng)的設(shè)計模式,后續(xù)計劃寫一篇ts版本的設(shè)計模式博客。
- 本片文章學(xué)習(xí)來源:
- 書籍《JavaScript設(shè)計模式與開發(fā)實踐》
- 雙越老師的:《Javascript 設(shè)計模式系統(tǒng)講解與應(yīng)用》
- 以及百度搜索借鑒的各種案例
以上就是javascript的23種設(shè)計模式總結(jié)大全的詳細(xì)內(nèi)容,更多關(guān)于javascript設(shè)計模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript中Date對象應(yīng)用之簡易日歷實現(xiàn)
這篇文章主要為大家詳細(xì)介紹了javascript中Date對象應(yīng)用之簡易日歷實現(xiàn),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-07-07
教你JS更簡單的獲取表單中數(shù)據(jù)(formdata)
這篇文章主要介紹了JS更簡單的獲取表單中數(shù)據(jù)(formdata),本文給大家分享的js獲取表單數(shù)據(jù)更簡潔,通過兩種方法結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
詳解javaScript中Number數(shù)字類型的使用
Number和Math都屬于JavaScript中的內(nèi)置對象,Number數(shù)字類型作為基礎(chǔ)數(shù)據(jù)類型,我們在開發(fā)過程中會經(jīng)常用到,包括數(shù)字精度的格式化,還有字符串轉(zhuǎn)換成數(shù)字等操作。本文將詳細(xì)講解其用法,感興趣的可以了解一下2022-04-04
JavaScript偽數(shù)組和數(shù)組的使用與區(qū)別
這篇文章主要給大家介紹了關(guān)于JavaScript偽數(shù)組和數(shù)組使用與區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05

