軟件設(shè)計-JS設(shè)計模式_第1頁
軟件設(shè)計-JS設(shè)計模式_第2頁
軟件設(shè)計-JS設(shè)計模式_第3頁
軟件設(shè)計-JS設(shè)計模式_第4頁
軟件設(shè)計-JS設(shè)計模式_第5頁
已閱讀5頁,還剩40頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

JS設(shè)計模式

設(shè)計模式是對軟件設(shè)計中普遍存在(反復(fù)出現(xiàn))的各種問題,所

提出的解決方案。設(shè)計模式并不直接用來完成代碼的編寫,而是描述

在各種不同情況下,要怎么解決問題的一種方案。

1.設(shè)計模式是被反復(fù)驗證的解決方案(最佳實踐),可以減少代

碼強耦合、硬編碼,有效的提高代碼的可擴展性和可維護性

2.設(shè)計模式是一種通用方案和思維,它不限制于特定編程語言

3.提供一種通用術(shù)語,方便交流,減少溝通中的理解成本。

設(shè)計模式的原則

做什么事都需要遵循一些準(zhǔn)則,設(shè)計模式也不例外。我們在設(shè)計一些

設(shè)計模式時,一般遵循如下6項基本原則,它們分別是:

Single-responsibilityprinciple(單一職責(zé)原貝!J)

每個軟件模塊都有且只有一個需要被改變的理由。

單一職責(zé)能夠降低模塊的復(fù)雜度,減少功能變更引起的關(guān)聯(lián)風(fēng)險,提

高代碼可讀性和可維護性。

Open-closedprinciple(開放封,閉原則)

系統(tǒng)應(yīng)該對擴展開放,對修改關(guān)閉。

如果軟件系統(tǒng)想要更容易被改變,那么其設(shè)計就必須允許新增代碼來

修改系統(tǒng)行為,而不是只能靠修改原來的代碼。

Liskovsubstitutionprinciple(里斯科夫替代原則)

一個子類應(yīng)該可以替換掉父類并且可以正常工作

Interfacesegregationprinciple(接口隔離原貝!J)

一個類對另一個類的依賴應(yīng)該建立在最小的接口上。

主要告誡軟件設(shè)計師應(yīng)該在設(shè)計中避免不必要的依賴。

Dependencyinversionprinciple(依賴反轉(zhuǎn)原貝!J)

高層策略性的代碼不應(yīng)該依賴實現(xiàn)底層細(xì)節(jié)的代碼,相反,底層細(xì)節(jié)

代碼應(yīng)該依賴高層策略性的代碼。

LawofDemeter(迪米特法則)

減少模塊間的依賴。只有使各個模塊之間的耦合盡量的低,才能提高

代碼的復(fù)用率。

【創(chuàng)建型】

模塊模式

模塊模式是在傳統(tǒng)軟件工程中為類提供私有和公有封裝的方法。

通過這種方式,讓一個對象擁有私有和公有的方法/變量,有效控制

對外暴露的api接口,屏蔽底層處理邏輯,但是由于js曾經(jīng)沒有訪

問修飾符,從技術(shù)的角度來說,我們不能稱js變量為私有和公有。

所以我們需要使用IIFE(即時調(diào)用函數(shù)表達式)、閉包和函數(shù)作用

域的方式來實現(xiàn)js的模塊模式。

varobj=(function(){

varcount=0;

return{

addCount:function(){count++},

getCount:function(){returncount}

})()

IIFE使得obj會獲得function中返回的對象,同時只有對象中的函

數(shù)可以閉包訪問到內(nèi)部的count變量,達到私有的目的。

最終外部采用調(diào)用模塊的公有屬性/方法來訪問和操作私有變量

obj.addCount()//1

obj.getCount()//1

obj.count//undefined

?應(yīng)用場景:

o需要管理大量私有變量/方法,希望屏蔽內(nèi)部處理邏輯,

只對外暴露接口的獨立模塊

?優(yōu)點:

o采用封裝的思想,只有本模塊才能享有私有變量,不會暴

露于外部模塊

。減少對全局作用域的影響,避免命名空間污染;

O模塊化有助于保持代碼的整潔、隔離和條理性。

?缺點:

O無法訪問方法調(diào)用后產(chǎn)生的私有變量

揭示模塊模式

某位同學(xué)(ChristianHeilmann)不滿模塊模式中必須創(chuàng)建新的公

共函數(shù)來調(diào)用私有函數(shù)和變量,所以略微改進了模塊模式為揭示模塊

模式:在本模塊的私有作用域范圍內(nèi)定義所有的方法和變量,將把

返回對象的屬性映射到我們想要公開的私有函數(shù)。

varobj=(function(){

varcount=0;

functionaddCount(){count++}

functiongetCount(){returncount)

return{

addCount:addCount,

getCount:getCount

})0

于模塊模式相比

?優(yōu)點:

O方法從private改為public非常簡單,只需要改屬性映

O返回的對象不包含任何函數(shù)定義,公開的函數(shù)和變量一目

了然,有助于提高代碼的可讀性

?缺點:

o導(dǎo)致私有函數(shù)升級困難。如果一個私有函數(shù)引用一個公有

函數(shù),在需要打補丁時,公有函數(shù)是不能被覆蓋的。eg:

functionrmpUrIBuiIder(){

varurlBase=〃http://my.default,domain/”;

var_build=function(relUrl){

returnurlBase+relUrl;

return{

urlBase:_urlBase,

build:build}

)

varbuilder=newrmpUrIBuiIder();

builder.urlBase=/zhttp://stackoverflow,com”;

console.log(builder.build('/questions");

//打印結(jié)果是"http://my.default,domain/questions”

//而不是//http://stackoverflow,com/questions^

單例模式

單例模式限制某個類只能被創(chuàng)建一次,并且需要提供一個全局的訪問

點。如果一個類的實例不存在,單例模式就會創(chuàng)建一個新的類實例。

如果實例存在,它只返回對該對象的引用。對構(gòu)造函數(shù)的任何重復(fù)調(diào)

用都會獲取相同的對象。

js中單例模式可以使用構(gòu)造函數(shù)實現(xiàn),例如:

letinstance=nulljfunctionUser(){

if(instance){

returninstance;

instance=this;

this,name='Peter';

this,age=25;

returninstance;

)

constuserl=newUser();

constuser2=newUser();

console,log(userl===user2);

//打印true

當(dāng)調(diào)用這個構(gòu)造函數(shù)時,它會檢查實例對象是否存在。如果對象不存

在,它就將這個變量賦給實例變量。如果對象存在,它只返回那個對

象。

單例也可以使用模塊模式實現(xiàn),例如:

constsingleton=(function(){

letinstance;

functioninit(){

return(

name:'Peter',

age:24,};

return{

getlnstance:function(){

if(!instance){

instance=init();

returninstance;

)|

})0;

constinstanceA=singleton,getlnstance();

constinstanceB=singleton,getlnstance();

console.log(instanceA===instanceB);//打印true

在上面的代碼中,我們通過調(diào)用singleton,getlnstance方法來創(chuàng)

建一個新實例。如果實例已經(jīng)存在,則該方法只是返回這個實例,如

果實例不存在,則調(diào)用initO函數(shù)創(chuàng)建一個新的實例。

小結(jié)

?應(yīng)用場景:

O應(yīng)用中有對象需要是全局的且唯一。比如頁面全局蒙層。

?優(yōu)點:

o適用于單一對象,只生成一個對象實例,避免頻繁創(chuàng)建和

銷毀實例,減少內(nèi)存占用。

?缺點:

O不適用動態(tài)擴展對象,或需創(chuàng)建多個相似對象的場景

簡單工廠模式

簡單工廠模式是一種創(chuàng)建型模式,它不需要指定所創(chuàng)建對象的確

切類或構(gòu)造函數(shù)。由工廠對象提供對外提供通用的工廠方法,然后根

據(jù)我們的特定需求/條件,生成不同的對象,在創(chuàng)建對象的過程中不

用公開實例化的邏輯。

舉例:在系統(tǒng)通知中我們會發(fā)送多種類型的消息,如郵件、微信、

短信、電話等。我們在創(chuàng)建不同消息的時候,可以采用工廠模式。通

過傳入的參數(shù)不同,輸出不同的消息對象。

classEmail{

constructor(options){

this,message=options,message||'我是emial信息';

this,type=options,type||'email',

this.sender=options,user;

this.receiver=options.receiver;

this.sendTime=options.sendTime|newDate()

Ml

I

classWeixin{

constructor(options){

this,msg=options,messageII'我是微信信息';

this,type=options,type||'weixin',

this.sender=options,user;

this.receiver=options,receiver;

classMessageFactory{

create(options){

switch(option,type){

case'emaiT:

returnnewEmail(options);

case'weixin':

returnnewWeixin(options);

default:

returnnull;

Ml

)

在這里我創(chuàng)建了一"1、Email類和一個Weixin類(帶有一些默認(rèn)

值),用于創(chuàng)建新的Email和Weixin對象。我還定義了一個

MessageFactory類,基于options對象中接收到的type屬性創(chuàng)建

和返回一個新的對象。

constfactory=newMessageFactory();

constemailMsg=factory,create({

type:'email',

message:'你好,有興趣了解下安利嗎’,

user:'xiaojia',

receiver:'xxl',

});

constweixinMsg=factory,create({

type:'weixin',

message:‘im賣保險',

user:'xiaojia',

receiver:'xx2',

});

我已經(jīng)創(chuàng)建了一個新的MessageFactory類的對象工廠。之后,我們

可以調(diào)用factory,create方法,傳入一個type屬性值為email

或weixin的options,來創(chuàng)建不同的消息對象。

?應(yīng)用場景:

o需要處理共享多個相同屬性的小型對象/組件

。可以根據(jù)不同的環(huán)境/參數(shù)生成對象的不同實例

o對象的類型已知、有限

?優(yōu)點:

O能解決創(chuàng)建多個相似對象的問題。

?缺點:

O違背了開放封閉原則。每增加一個產(chǎn)品,都需要修改工廠

類的代碼

o對象的類型不知道

工廠模式

在簡單工廠模式中,一個工廠類負(fù)責(zé)所有產(chǎn)品對象的創(chuàng)建,這個

工廠類的職責(zé)大大增加,可能客戶端對于某些產(chǎn)品的創(chuàng)建方式會有不

同的要求,這樣的話,就要不斷的修改工廠類,增加相應(yīng)的判斷邏輯,

不利于后期的代碼維護。所以我們將簡單工廠模式進一步抽象化,實

現(xiàn)工廠模式:讓工廠子類去實現(xiàn)抽象工廠類的接口,由每個具體的工

廠類負(fù)責(zé)創(chuàng)建單獨的產(chǎn)品,如果有新的產(chǎn)品加進來,只需要增加一個

具體的創(chuàng)建產(chǎn)品工廠類和具體的產(chǎn)品類就可以了,不會影響到已有的

其他代碼,代碼量也不會變大,后期維護更加容易,增加了系統(tǒng)的可

擴展性。

我們基于上述MessageFactory的代碼進行修改

classMessageFactory{

create(options){

thrownewErrorC需要create方法’)

I

classEmaiIMsgFactoryextendsMessageFactory{

create(options){

returnnewEmail(options)

HH

)

?應(yīng)用場景:

o需要處理多個復(fù)雜產(chǎn)品對象

。不同的工廠和產(chǎn)品可以提供客戶端不同的服務(wù)或功能

o產(chǎn)品對象的類型會動態(tài)增加

?優(yōu)點:

O克服了簡單工廠違背開放-封閉原則的缺點,后期維護更

加容易。

?缺點:

o每增加一個產(chǎn)品,相應(yīng)的也要增加一個子工廠,加大了額

外的開發(fā)量

原型模式

原型模式主要是用于創(chuàng)建重復(fù)的對象。通俗點講就是創(chuàng)建一個共享的

原型,并通過拷貝這些原型創(chuàng)建新的對象。

js中可以通過Object,create來實現(xiàn)

varmyCar={

name:"FordEscort”,

drive:function(){

console,log("Weeee.I'mdriving!,z);

I^H

panic:function(){

console.logC^Wait.Howdoyoustopthisthing?");

HH

);

//UseObject,createtoinstantiateanewcar

varyourCar=Object,create(myCar);

//Nowwecanseethatoneisaprototypeoftheother

console.log(yourCar.name);

?應(yīng)用場景:

o需要大量復(fù)制已存在的對象,對象間又相互獨立

?優(yōu)點:

o/實現(xiàn)功能吧,沒想出什么優(yōu)點

?缺點:

【結(jié)構(gòu)型】

代理模式

代理模式的核心是為對象提供一個代理對象,來控制對目標(biāo)對象的訪

問,客戶其實訪問的是這個代理對象。這樣代理對象就可以對請求做

出一些處理之后,再將請求轉(zhuǎn)交給本體對象。

代理模式中常見的有保護代理、虛擬代理、緩存代理。

保護代理主要是限制了訪問主體的行為。下面以過濾消息中的敏感信

息作為簡單的例子

//主體,發(fā)送消息functionsendMsg(msg){

console,log(msg);

|

//代理,對消息進行過濾functionproxySendMsg(msg){

//無消息則直接返回

if(typeofmsg==='undefined'){

console.log(,deny,);

return;

//有消息則進行過濾

msg=(''+msg).replace(/敏感信息/g,'****');

sendMsg(msg);

sendMsg('敏感信息');〃敏感信息

proxySendMsgC敏感信息');//****

proxySendMsgO;//deny

虛擬代理主要是在訪問行為中加入一些額外操作,最常見的例子有函

數(shù)防抖。我們的目的是去觸發(fā)fn,但是debounce函數(shù)會對清除老的

timer,并且將fn放到新的timer中。

//函數(shù)防抖,頻繁操作中不處理,直到操作完成之后(再過delay的

時間)才處理functiondebounce(fn,delay){

delay=delay||200;

vartimer=null;

returnfunction(){

vararg=arguments;

//每次操作時,清除上次的定時器

clearTimeout(timer);

timer=null;

//定義新的定時器,一段時間后進行操作

timer=setTimeout(function(){

fn.apply(this,arg);

delay);

IH

};

另外,ES6所提供Proxy構(gòu)造函數(shù)也能夠讓我們輕松的使用代理模式。

?應(yīng)用場景:

O訪問對象比較復(fù)雜,并且需要對訪問行為進行控制

?優(yōu)點:

O依托代理,可額外添加擴展功能,而不修改本體對象,符

合”開發(fā)-封閉原則”

O對象職能粒度細(xì)分,函數(shù)功能復(fù)雜度降低

?缺點:

O額外代理對象的創(chuàng)建,增加部分內(nèi)存開銷

o處理請求速度可能有差別,非直接訪問存在開銷

適配器模式

在生活中我們常常會用到電源適配器、Type-C轉(zhuǎn)接頭,這些器件

都是為了不同的設(shè)備能夠兼容協(xié)作。

適配器模式的目的也是這樣:將一個對象的接口(方法或?qū)傩裕?/p>

轉(zhuǎn)化成客戶希望的另外一個接口(方法或?qū)傩裕?,使得原本由于接?/p>

不兼容而不能一起工作的那些對象可以正常協(xié)作。

工作中最常見的就是各種數(shù)據(jù)格式的轉(zhuǎn)換,以傳遞給不同的插件

方法。

functiondataConvenrt(data){

//dosomething

returndealData

)

適配器模式也非常適用于跨瀏覽器兼容,例如強大的jQuery封

裝了事件處理的適配器,解決跨瀏覽器兼容性問題,極大簡化我們?nèi)?/p>

常編程操作。

functionon(target,event,callback){

if(target.addEventListener){

//標(biāo)準(zhǔn)事件監(jiān)聽

target.addEventListener(event,callback);

}elseif(target.attachEvent){

//IE低版本事件監(jiān)聽

target.attachEvent(event,callback)

}else{

//低版本瀏覽器事件監(jiān)聽

target['on${

event

}']=callback

)

,與代理模式的差異:

O兩者都會在訪問原始對象前對請求數(shù)據(jù)進行處理。但是適

配器的目的是為了兼容不同對象的接口/屬性,而代理模

式是為了控制訪問行為。

?應(yīng)用場景:

o需要兼容不同對象的接口。比如跨瀏覽器兼容、整合第三

方SDK、新老接口兼容

?優(yōu)點:

。兼容性好,保證外部可統(tǒng)一接口調(diào)用

?缺點:

O額外對象的創(chuàng)建,非直接調(diào)用,存在一定的開銷(且不像

代理模式在某些功能點上可實現(xiàn)性能優(yōu)化)。

裝飾器模式

我們在打游戲時,游戲角色會帶上各種buff、debuffo這種屬性并

不是繼承而來,而是游戲過程中動態(tài)增加的。這種場景就非常適合裝

飾器模式。

裝飾器模式可以動態(tài)地給對象添加一些新功能。它是一種“即用即付”

的方式,能夠在不改變對象自身的基礎(chǔ)上,在程序運行期間給對象動

態(tài)地添加職責(zé)。

JS中最簡單的裝飾器就是重寫對象的屬性

varA={

score:10

);

A.score='分?jǐn)?shù):'+A.score;

也可以通過構(gòu)造函數(shù)和原型的方式來實現(xiàn)裝飾器,并且經(jīng)過多重包裝

可以形成一條裝飾鏈

functionPerson(){}

Person,prototype,skill=function(){

console.logC(數(shù)學(xué)');

);

//裝飾器,還會音樂functionMusicDecorator(person){

this,person=person;

I

MusicDecorator.prototype,skill=function(){

this,person,skill();

console.log('音樂’);

};

//裝飾器,還會跑步functionRunDecorator(person){

this.person=person;

|

RunDecorator.prototype,skill=function(){

this,person,skill();

console,log('跑步');

);

varperson=newPerson();

//裝飾一下

varpersonl=newMusicDecorator(person);

//再裝飾一下

personl=newRunDecorator(personl);

person,skill();//數(shù)學(xué)

personl.skill();//數(shù)學(xué)音樂跑步

最新的ECMA中有裝飾器(Decorator)的提案,它是一種與類(class)

相關(guān)的語法,用來注釋或修改類和類方法。有興趣的同學(xué)可以看下阮

一峰的介紹

?應(yīng)用場景:動態(tài)地給對象添加一些新功能,并且不改變對象本

身。

?優(yōu)點:同上

?缺點:/

組合模式

組合模式是為了解決大型/復(fù)雜對象的結(jié)構(gòu)問題,用小的子對象來

構(gòu)建更大的對象,而這些小的子對象本身也許是由更小的“孫對象”

構(gòu)成的。

這種組合有具有一定的要求和條件:1.對象可以用樹形結(jié)構(gòu)來表

示;2.根對象和葉對象屬于同一類,需要具有相同的接口。

最常見的例子有遞歸掃描文件夾中的文件

//文件夾組合對象functionFolder(name){

this,name=name;

this,parent=null;

this,files=口;

|

Folder,prototype={

constructor:Folder,

add:function(file){

file,parent=this;

this,files,push(file);

returnthis;

),

scan:function(){

//委托給葉對象處理

for(vari=0;i<this,files,length;++i){

this.files[i].scan();

HI

remove:function(file){

if(typeoffile===,undefined(){

this,files=[];

return;

for(vari=0;i<this,files.length;++i){

if(this,files[i]===file){

this,files.splice(i,1);

};

//文件葉對象functionFile(name){

this,name=name;

this,parent=null;

Ftotype={

constructor:File,

add:function(){

console.log(,文件里面不能添加文件’);

I^H

scan:function(){

varname=[];

varparent=this,parent;

while(parent){

name,unshift(parent,name);

parent=parent,parent;

console,log(name.join(,/'));

);

我們在構(gòu)造組合對象和葉對象后進行實例化,插入數(shù)據(jù)

varweb=newFolder('Web');

varfe=newFolder('前端');

varcss=newFolder('CSS');

varjs=newFolder('js');

varrd=newFolder('后端');

web.add(fe).add(rd);

varfilel=newFile('HTML權(quán)威指南.pdf');

varfile2=newFile('CSS權(quán)威指南.pdf');

varfile3=newFile('JavaScript權(quán)威指南.pdf');

varfile4=newFile('MySQL基礎(chǔ).pdf');

varfile5=newFile('Web安全.pdf');

varfile6=newFile('Linux菜鳥.pdf');

css.add(file2);

fe.add(filel).add(file3).add(css).add(js);

rd.add(file4).add(file5);

web.add(file6);

rd.remove(file4);

//掃描

web.scan();

//結(jié)果

//Web/前端/HTML權(quán)威指南.pdf

//Web/前端/JavaScript權(quán)威指南.pdf

//Web/前端/CSS/CSS權(quán)威指南.pdf

//Web/后端/Web安全.pdf

//Web/Linux菜鳥.pdf

?應(yīng)用場景:

O可以組合成樹形結(jié)構(gòu)的復(fù)雜對象,并且需要對外提供一致

性的操作接口。

O優(yōu)化處理遞歸或分級數(shù)據(jù)結(jié)構(gòu)

?優(yōu)點:

。忽略組合對象和單個對象的差別,對外一致接口使用;

O解耦調(diào)用者與復(fù)雜元素之間的聯(lián)系,處理方式變得簡單。

?缺點

O樹葉對象接口一致,無法區(qū)分,只有在運行時方可辨別;

外觀模式

外觀模式是一種非常簡單而又無處不在的模式。外觀模式對外提

供一個統(tǒng)一高層的方法,來訪問子系統(tǒng)中的一群接口,能夠隱藏其底

層的復(fù)雜性。

通俗點來解釋這個模式:今天瑞瑞準(zhǔn)備叫外賣。單點是一種方式,

但面對各種單品也是無從下手,這時瑞瑞就會選擇看套餐,因為這個

是已經(jīng)搭配好的,并且沒有選擇糾結(jié)。這個套餐就是我們的外觀模式

雛型,把一些細(xì)碎的東西收起來,統(tǒng)一對外開放。

舉個

fcuntiontosllpload(){

//先獲取上傳token

//執(zhí)行上傳

//上傳后將附件數(shù)據(jù)發(fā)送給業(yè)務(wù)后臺服務(wù)

?優(yōu)點:

O簡化接口,易于使用

O使用者與底層代碼解耦

.缺點:

O隱藏底層邏輯,不易調(diào)試

O子系統(tǒng)需要能夠提供穩(wěn)定服務(wù)

?和組合模式的差別:外觀模式內(nèi)部需要知道具體哪幾個對象,

組合模式是取全量葉節(jié)點

?和中介模式的差別:外觀模式處理的是類之間的復(fù)雜依賴關(guān)系,

中介模式處理的是對象之間復(fù)雜的通信機制

【行為型】

觀察者模式

觀察者模式是一種行為型模式,關(guān)注的是對象之間的通訊,觀察者模

式就是觀察者和被觀察者之間的通訊,主要用于一個對象(目標(biāo))去

維持多個依賴于它的對象(觀察者),將相關(guān)變更的事件自動通知給

他們的場景。

functionSubject(){this.observerList=[]//初始化觀察者

隊列}functionObserver(){this,update=

function(ctx){//dosomethingwihtctx}}//增加

觀察者Subject,prototype.addObserver=

function(observer){this.observerList.push(observer)}//

通知觀察者Subject,prototype,notify=

function(ctx){this.observerList.forEach(observer=>{

observer.update(ctx)})}

?應(yīng)用場景:

o對一個對象狀態(tài)的更新,需要其他對象同步更新,而且其

他對象的數(shù)量動態(tài)可變。

O對象僅需要將自己的更新通知給其他對象而不需要知道

其他對象的細(xì)節(jié)。

。比如采購中,尋源結(jié)果審批通過后,會要通知相關(guān)采購負(fù)

責(zé)人、自動創(chuàng)建合同信息等后續(xù)操作。

?優(yōu)點:

O觀察者模式在被觀察者和觀察者之間建立一個抽象的耦

合。被觀察者角色所知道的只是一個具體觀察者列表,每

一個具體觀察者都符合一個抽象觀察者的接口。

O觀察者模式支持廣播通訊。被觀察者會向所有的登記過的

觀察者發(fā)出通知,

?缺點:

O如果一個被觀察者對象有很多的直接和間接的觀察者的

話,同步通知花費時間會很長。

。如果在被觀察者之間有循環(huán)依賴的話,被觀察者會觸發(fā)它

們之間進行循環(huán)調(diào)用,導(dǎo)致系統(tǒng)崩潰。

發(fā)布訂閱模式

其實24種基本的設(shè)計模式中并沒有發(fā)布訂閱模式,他只是觀察者

模式的一個別稱。但是經(jīng)過時間的沉淀,似乎他已經(jīng)強大了起來,已

經(jīng)獨立于觀察者模式,成為另外一種不同的設(shè)計模式。

在現(xiàn)在的發(fā)布訂閱模式中,稱為發(fā)布者的消息發(fā)送者不會將消息

直接發(fā)送給訂閱者,這意味著發(fā)布者和訂閱者不知道彼此的存在。在

發(fā)布者和訂閱者之間存在第三個組件,稱為調(diào)度中心或事件通道,它

維持著發(fā)布者和訂閱者之間的聯(lián)系,過濾所有發(fā)布者傳入的消息并相

應(yīng)地分發(fā)它們給訂閱者。

舉一個例子,你在微博上關(guān)注了A,同時其他很多人也關(guān)注了A,

那么當(dāng)A發(fā)布動態(tài)的時候,微博就會為你們推送這條動態(tài)。A就是發(fā)

布者,你是訂閱者,微博就是調(diào)度中心,你和A是沒有直接的消息往

來的,全是通過微博來協(xié)調(diào)的(你的關(guān)注,A的發(fā)布動態(tài))。

我們每個人在編程的時候都用過發(fā)布訂閱模式,比如DOM事件綁定

addEventListener^vue數(shù)據(jù)雙向綁定。

考慮到面試經(jīng)常問到,我就不寫demo代碼了。。

優(yōu)點:

O相較于觀察者模式,發(fā)布/訂閱發(fā)布者和訂閱者的耦合性

更低

O事件通知分發(fā)是異步的

?缺點:

O發(fā)布者不知道所有訂閱者是否成功收到通知

?和觀察者模式的區(qū)別:

O在觀察者模式中直接通信。然而在發(fā)布訂閱模式中只有

通過消息代理進行通信。

O發(fā)布/訂閱模式相比于觀察者模式多了一個中間媒介,因

為這個中間媒介,發(fā)布者和訂閱者的關(guān)聯(lián)更為松耦合

O觀察者模式大多數(shù)時候是同步的,比如當(dāng)事件觸發(fā),

Subject就會去調(diào)用觀察者的方法。而發(fā)布-訂閱模式大

多數(shù)時候是異步的。

策略模式

策略模式的意義是定義一系列的算法,把它們一個個封裝起來,

并且使它們可相互替換。具體實現(xiàn)是由多個策略類實現(xiàn)具體算法,然

后由一個環(huán)境類來通過請求參數(shù)決定使用哪些策略。

策略模式利用組合、委托等方法,可以有效避免多個if條件語句。

適合用于組合一系列算法,或者組合一系列相同目的的業(yè)務(wù)規(guī)則。

舉個業(yè)務(wù)場景,我們?nèi)绻鶕?jù)不同銷售等級來計算工資,可能

會寫出這么僵硬的代碼:

varcalculateBouns=function(salary,level){

if(level==='A'){

returnsalary*4;

if(level==='B'){

returnsalary*3;

if(level==='C'){

returnsalary*2;

)

};

//調(diào)用如下:

console.log(calculateBouns(4000,'A());//16000

console,log(calculateBouns(2500,'B'));//7500

這段代碼包含多個if-else,并且也缺乏彈性。如果還來個D等

級,或者A等級的計算規(guī)則需要改變,那么就需要在calculateBouns

方法中去修改,違背了開-閉原則。

我們基于策略模式重構(gòu)一下?,F(xiàn)在我們將具體策略封裝起來,可

以看到代碼職責(zé)更新分明,代碼變得更加清晰。并且代碼的可拓展性

更強,增加/修改策略,只需要在策略集合。bj中去維護.

varobj={

"A":function(salary){

returnsalary*4;

“B〃:function(salary){

returnsalary*3;

“C〃:function(salary){

returnsalary*2;

IH

);

varcalculateBouns=function(level,salary){

returnobj[level](salary);

E

console,log(calculateBouns,10000));//40000

策略模式也常用于表單驗證,定義不同的規(guī)則校驗方法,調(diào)用驗證的

時候只需要傳入規(guī)則名即可。

?應(yīng)用場景:

O調(diào)用方依賴1個或多個策略

O業(yè)務(wù)場景有多種條件處理方案

?優(yōu)點:

O減少if-else,代碼更整潔直觀

O提供開放-封閉原則,代碼更容易理解和擴展

?缺點:

O策略集合通常會比較多,需要事先了解定義好所有的情況

。使用方必須理解不同策略的區(qū)別

模板模式

模板模式為了解決不同對象的相同行為的場景,它由兩部分組成:

抽象父類+具體的實現(xiàn)子類。抽象父類定義抽象方法和具體運行策

略,來制定子類方法的運行順序和機制;具體子類來重寫父類的抽象

方法,實現(xiàn)不同的處理邏輯。

舉個業(yè)務(wù)場景例子:在頭條面試的流程是:筆試-技術(shù)面-leader

面-hr面。百度的面試流程也是這樣。那我們就可以用模板模式,創(chuàng)

建一個抽象的面試類,在面試類中定義面試的流程,然后由頭條面試

/百度面試對象繼承和重寫具體的面試步驟內(nèi)容,比如說頭條筆試是

考算法、百度筆試是考css。并且可以通過鉤子函數(shù)來解決是否需要

某些運行步驟。

定義面試抽象類,init為具體子類方法運行策略,其他方法是抽

象方法。

//面試functionInterview(companyName){

this.companyName=companyName

|

Interview,prototype={

constructor:Interview,

//模板,按順序執(zhí)行

init:function(){

this.writtenTest();

this.techTest();

this.leaderTest();

if(this.needHrTest()){//鉤子函數(shù)

this.hrTest();

//筆試

writtenTest:function(){

■thrownewError('必須傳遞writtenTest方法');

//面試

techTest:function(){

■thrownewError('必須傳遞techTest方法');

//leader面

leaderTest:function(){

■thrownewError('必須傳遞leaderTest方法');

//是否需要hr面

needHrTest:function(){

Ireturntrue

//hr面

hrTest:function(){

thrownewError('必須傳遞hrTest方法');

};

子類實現(xiàn)具體抽象方法,并調(diào)用init方法執(zhí)行面試行為。

varTouTiaoInterview=function(){};

TouTiaoInterview.prototype=newInterview();

//子類重寫方法實現(xiàn)自己的業(yè)務(wù)邏輯

TouTiaoInterview.prototype.writtenTest=function(){

console,log(〃先來個紅黑樹〃);

I

TouTiaoInterview.prototype.techTest=function(){

console,log(〃你對什么比較了解“);

)

TouTiaoInterview.prototype.leaderTest=function(){

console,log("leader談笑風(fēng)生“);

|

TouTiaoInterview.prototype.hrTest=function(){

console,log(〃人力資源太不給力了,我等的花兒都謝了!!〃);

|

varTouTiaoInterview=newTouTiaoInterview();

TouTiaoInterview.init();

?應(yīng)用場景:

O在多個子類擁有相同的方法,并且這些方法邏輯相同時

O在程序的主框架相同,細(xì)節(jié)不同的場合下

?優(yōu)點:

O利用模板模式可以將相同處理邏輯的代碼放到抽象父類

中,提高了代碼的復(fù)用性。

O將不同的邏輯放到不同的子類中,通過子類的擴展增加新

的行為,提高了代碼的擴展性。

?缺點:

O類數(shù)量增加間接增加了系統(tǒng)的復(fù)雜性

O因為繼承關(guān)系的自身缺點,如果父類添加一個新的抽象方

法,所有子類都要實現(xiàn)一遍。

狀態(tài)模式

對象的狀態(tài)會影響對象的行為,并且行為中伴隨著狀態(tài)的改變時,

就非常適合狀態(tài)模式。把事物的每種狀態(tài)都封裝成單獨的類,跟此種

狀態(tài)有關(guān)的行為都被封裝在這個類的內(nèi)部

我們每天也都處于狀態(tài)模式下:工作=>睡覺=>工作=>睡覺。那我

們可以定一個person對象和work、sleep兩種狀態(tài)

functionPerson(name){

this,name=namethis,currentstate=null;

//狀態(tài)

this.workState=newWorkState(this)this.sleepState=new

SleepState(this)

this,init()

|

Person,prototype,init=function(){

this,currentstate=this.workState;//設(shè)置初始狀態(tài)

this,currentstate,behaviour0;//開始初始狀態(tài)的行為

E

Person,prototype.setState=function(state){

this,currentstate=state;

this,currentstate,behaviour();

|

//工作狀態(tài)functionWorkState(person){

this,person=person

I

WorkState.prototype,behaviour=function(){

console.log(this.person,name+'上班摸魚了8小時')

//觸發(fā)[睡覺]狀態(tài)

setTimeout(()=>{

this,person.setState(this,person.sleepState);

},

2*1000);

|

//睡覺狀態(tài)functionSleepState(person){

this,person=person

|

SleepStotype,behaviour=function(){

console.log(this.person,name+'睡了了14小時')

//觸發(fā)[睡覺]狀態(tài)

setTimeout(()=>{

this,person.setState(this,person.workState);

I^H

2*1000);

varperson=newPerson('老王')

?應(yīng)用場景:

o對象和外部互動時,會改變內(nèi)部狀態(tài),并且不同的狀態(tài)會

發(fā)生不同的行為

o在運行過程中這個對象的狀態(tài)會經(jīng)常切換

?優(yōu)點

O一個狀態(tài)狀態(tài)對應(yīng)行為,封裝在一個類里,更直觀清晰,

增改方便

O狀態(tài)與狀態(tài)間,行為與行為間彼此獨立互不干擾

。避免事物對象本身不斷膨脹,條件判斷語句過多

?缺點:

O需要將事物的不同狀態(tài)以及對應(yīng)的行為拆分出來,有時候

會無法避免動作拆不明白了,過度設(shè)計

O與策略模式的不同:狀態(tài)模式經(jīng)常會在處理請求的過程中

更改上下文的狀態(tài),而策略模式只是按照不同的算法處理

算法邏輯

中介者模式

程序中存在對象,所有這些對象都按照某種關(guān)系和規(guī)則來通信。

當(dāng)程序的規(guī)模增大,對象會越來越多,它們之間的關(guān)系也越來越復(fù)雜,

難免會形成網(wǎng)狀的交叉引用。當(dāng)改變或刪除其中一個對象的時候,很

可能需要通知所有引用到它的對象。這樣的硬編碼方式會導(dǎo)致代碼和

對象的邏輯關(guān)系難以維護。

□中介者對象可以讓各個對象之間不需要顯示的相互引用,從而使

其耦合松散,而且可以獨立的改變它們之間的交互。所有的相關(guān)對象

都通過中介者對象來通信,而不是互相引用,所以當(dāng)一個對象發(fā)生改

變時,只需要通知中介者對象即可

「采購部就是一個典型的中介者模式。業(yè)務(wù)部門A找采購部提采購

需求,由采購部去發(fā)布公告召集B、C、D

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論