摘要:作為一個前端新人,學習了設(shè)計模式以后,希望能從源頭上,用淺顯易懂的語言來解釋它。創(chuàng)建型設(shè)計模式創(chuàng)建型設(shè)計模式故名思意,這些模式都是用來創(chuàng)建實例對象的。這就是簡單工廠模式。這就是工廠方法模式。
作為一個前端新人,學習了設(shè)計模式以后,希望能從源頭上,用淺顯易懂的語言來解釋它。當然不一定是正確的,只是我個人對設(shè)計模式的一點淺顯理解。創(chuàng)建型設(shè)計模式
創(chuàng)建型設(shè)計模式:故名思意,這些模式都是用來創(chuàng)建實例對象的。
單例模式首先我們需要理解什么是單例。
單:指的是一個。
例:指的是創(chuàng)建的實例。
單例:指的是創(chuàng)建的總是同一個實例。也就是使用類創(chuàng)建的實例始終是相同的。
我們先看下面的一段代碼:
class Person{ constructor(){} } let p1 = new Person(); let p2 = new Person(); console.log(p1===p2) //false
上面這段代碼,定義了一個Person類,通過這個類創(chuàng)建了兩個實例,我們可以看到最終這兩個實例是不相等的。也就是說,通過同一個類得到的實例不是同一個(這本就是理所應(yīng)當),但是如果我們想始終得到的是同一個實例,那么這就是單例模式。那么應(yīng)該如何實現(xiàn)單例模式了:
想要實現(xiàn)單例模式,我們需要注意兩點:
需要使用return。使用new的時候如果沒有手動設(shè)置return,那么會默認返回this。但是,我們這里要使得每次返回的實例相同,也就是需要手動控制創(chuàng)建的對象,因此這里需要使用return。
我們需要每次return的是同一個對象。也就是說實際上在第一次實例的時候,需要把這個實例保存起來。再下一個實例的時候,直接return這個保存的實例。因此,這里需要用到閉包了。
代碼實現(xiàn)如下:
(function(){ let instance = null; return class{ constructor(){ if(!instance){ //第一次創(chuàng)建實例,那么需要把實例保存 instance = this; }else{ return instance; } } } })() let p3= new Person(); let p4 = new Person(); console.log(p3===p4) //true
從上面的代碼中,我們可以看到在閉包中,使用instance變量來保存創(chuàng)建的實例,每次返回的都是第一次創(chuàng)建的實例。這樣的話就實現(xiàn)了無論創(chuàng)建多少次,創(chuàng)建的都是同一個實例,這就是單例模式。
工廠模式對于工廠來說,我們的印象可能是里面具有各種各樣的模具,根據(jù)你想要的產(chǎn)品的模型,生產(chǎn)你需要的產(chǎn)品。比如說你請工廠幫你加工一個產(chǎn)品,你只需要告訴工廠你這個產(chǎn)品的結(jié)構(gòu),工廠就會有對應(yīng)的模型幫你生產(chǎn),你不需要去關(guān)心它具體是怎么加工的。同樣工廠模式也是這樣,(工廠模式也是創(chuàng)建型設(shè)計模式,用于創(chuàng)建實例對象的)你不需要自己去找對應(yīng)的類來創(chuàng)建實例,你只需要告訴工廠類你要創(chuàng)建什么實例,他就會返回你需要的實例對象。
工廠模式根據(jù)抽象程度的不同,分為三種:
簡單工廠模式
工廠方法模式
抽象工廠模式
簡單工廠模式
定義:定義一個工廠類,通過工廠函數(shù),根據(jù)傳入的參數(shù)不同,返回不同的實例??聪旅娴拇a:
//學生類 class Student{ constructor(name,age){ this.name = name; this.age = age; } showName(){ console.log(this.name) } } //老師類 class Teacher{ constructor(name,age){ this.name = name; this.age = age; } showName(){ console.log(this.name) } } //警察類 class Policeman{ constructor(name,age){ this.name = name; this.age = age; } showName(){ console.log(this.name) } } 根據(jù)類創(chuàng)建對象 const s1 = new Student("王小一",24); const t1 = new Teacher("李一老師",39); const p1= new Policeman("張一警官",40);
我們可以看到,上面代碼中定義了三個類,學生類,老師類和警察類。而且它們具有相同的屬性和方法。當我們需要創(chuàng)建學生實例時,我們調(diào)用學生類。當我們需要創(chuàng)建老師實例時,我們調(diào)用老師類,當我們需要創(chuàng)建警察實例,我們調(diào)用警察類。假設(shè)我們有更多的人物類,它們具有相同的功能,那么當我們需要創(chuàng)建實例的時候,我們同樣需要調(diào)用相對應(yīng)的類。事實上,這些類實現(xiàn)的都是相同的功能,那么我們可不可以把所有的創(chuàng)建這些人物實例都通過一個類來實現(xiàn)了。我們嘗試將代碼修改為如下:
//學生類 class Student{ constructor(name,age){ this.name = name; this.age = age; } showName(){ console.log(this.name) } } //老師類 class Teacher{ constructor(name,age){ this.name = name; this.age = age; } showName(){ console.log(this.name) } } //警察類 class Policeman{ constructor(name,age){ this.name = name; this.age = age; } showName(){ console.log(this.name) } } //工廠類 class Factory{ let obj = null; //工廠函數(shù) constructor(role,name,age){ switch(role){ case "student": obj = new Student(name,age); break; case "teacher": obj = new Teacher(name,age); break; case "policeman": obj = new Policeman(name,age); break; } } return obj; } const s2 = new Factory("student","王小二",25); const t2 = new Factory("teacher","李二老師",39); const p2 = new Factory("policeman","張二警官",40);
從上面的代碼中,我們可以看到我們同樣定義了學生類,老師類,警察類這三個類,但是我們創(chuàng)建實例時通過Factory這個類,不再通過相對應(yīng)的人物類了。這個Factory類就是工廠類,我們觀察工廠類的實現(xiàn),發(fā)現(xiàn)里面是一個工廠函數(shù)(這里直接使用了constructor,也可以自己定義工廠函數(shù)),通過傳遞給工廠函數(shù)的參數(shù)不同,返回不同的實例。這就是簡單工廠模式。
簡單工廠模式總結(jié):
實現(xiàn):從上面的代碼中我們可以知道,所謂簡單工廠模式就是一個工廠類和一個工廠函數(shù),通過傳入?yún)?shù)的不同,返回不同的實例。
特點:1. 需要創(chuàng)建的類較少,因為需要根據(jù)傳入的參數(shù)來判斷返回的實例,如果類太多,那么就會導(dǎo)致邏輯復(fù)雜。2. 不需要關(guān)注實例的創(chuàng)建過程,只需要傳入相對應(yīng)的值即可。
適用場景:舉一個生活中實際的使用場合,假如我們上體育課需要去拿籃球,足球和排球,我們可以自己去一個一個找對應(yīng)的球(類似于上面通過自己來創(chuàng)建對象),也可以通過管理員,告訴管理員需要什么樣的球,至于管理員是怎么找到這個相對應(yīng)的球,就與我們不相關(guān)了。這個管理員就是工廠類。
缺點:從簡單工廠模式的特點中我們可以知道,簡單工廠模式適合于創(chuàng)建的類較少,一旦需要的類較多,邏輯就會復(fù)雜。而且一旦需要添加新的類,就得重新修改工廠類,這樣顯得非常不方便。
工廠方法模式
工廠方法模式是對簡單工廠的進一步優(yōu)化, 在工廠方法模式中,我們不再提供一個統(tǒng)一的工廠類來創(chuàng)建所有的對象,而是針對不同的對象提供不同的工廠。也就是說每個對象都有一個與之對應(yīng)的工廠。說的好像挺復(fù)雜,其實在我看來他就是解決簡單工廠模式存在的不方便添加新的類,因為添加新的類以后需要修改工廠函數(shù)。而工廠方法模式就是解決這個問題,看下面的代碼:
let Factory = (function(){ let s = { Student(name,age){ this.name = name; this.age = age; return this; }, Teacher(name,age){ this.name = name; this.age = age; return this; }, Policeman(name,age){ this.name = name; this.age = age; return this; }, //在這里添加新的類 Doctor(name,age){ this.name = name; this.age = age; return this; } } return class { //工廠函數(shù)根據(jù)傳進來的來的參數(shù)不同而不同。 constructor(type,name,age){ if(s.hasOwnProperty(type)){ return s[type].call(this,name,age) }else{ throw new Error(`不存在${type}類`) } } } })() let s3 = new Factory("Student","王小三",25); let t3 = new Factory("Teacher","李三老師",25); let p3 = new Factory("Policeman","張三警官",28); let d3 = new Factory("Doctor","楊醫(yī)生",33);
從上面的代碼中,我們可以看到,相比于簡單工廠函數(shù),工廠方法模式的工廠函數(shù)不是固定的,而是根據(jù)type不同而不同。當我們需要添加新的類時,只需要在s對象中添加實例即可,不需要修改工廠函數(shù)。這樣的話就不會因為需要添加新的類,而修改過多的代碼邏輯。這就是工廠方法模式。其實就是對簡單工廠模式的優(yōu)化而已。
建造者模式(builder pattern)提到建造者,我們可能第一印象就是城市中建高樓大廈,建房子一般設(shè)計到業(yè)主,項目負責人,建筑隊工人。業(yè)主告訴項目負責人,需要建造什么樣的房子,項目負責人告訴工人應(yīng)該怎么修建,不同工人完成不同工作。大家各司其職,最終得到一個建成的房子。雖然在建房子過程中,各個部分都需要打交道,但是更多的還是各司其職,每個人完成每個人的工作。其實就是把一個復(fù)雜的建房子工程,拆分成了若干部分由不同人來完成。在編程中也是如此,如果我們需要創(chuàng)建一個復(fù)雜的對象,可以把這個對象進行構(gòu)建,使得不同部分,完成不同功能。
建造者模式定義:將一個復(fù)雜的對象分解成多個簡單的對象來進行構(gòu)建,將復(fù)雜的構(gòu)建層與表示層分離,使得相同的構(gòu)建過程可以創(chuàng)建不同的表示的模式便是建造者模式??炊x通常是無法直接理解這種設(shè)計模式的,還是直接看代碼:
假設(shè)我們需要創(chuàng)建一輛車,車的組件包括車名,車牌號,車的價錢,車的引擎等。我們先看不使用建造者模式應(yīng)該如何創(chuàng)建:
class Car{ constructor(){ this.name = ""; this.number = ""; this.price = ""; this.engine = ""; } //設(shè)置名字 setName(){ this.name = "寶馬"; } //設(shè)置車牌號 setNumber(){ this.number = "888888" } //設(shè)置價錢 setPrice(){ this.price = "100萬" } //設(shè)置引擎 setEngine(){ this.engine = "最好的引擎" } //車的創(chuàng)建 getCar(){ this.setName(); this.setNumber(); this.setPrice(); this.setEngine(); } } //創(chuàng)建一個車: let car = new Car(); car.getCar(); console.log(car)
從上面的代碼中,我們可以看到創(chuàng)建一輛車需要的元素包括:name,number,price,engine。每一種元素又需要setxx來多帶帶實現(xiàn),最終車的創(chuàng)建還需要通過getCar來完成。也就是說在創(chuàng)建車的過程中需要的元素較多,創(chuàng)建過程相互影響,相互耦合。這只是簡單的4個元素,而且耦合性也不是太高,但是假設(shè)元素他特別多,代碼的耦合性也特別多,如果出現(xiàn)添加新的要素,那么實現(xiàn)起來要修改的代碼就太多了。因此,我們需要對代碼進行解耦,這就是建造者模式。
上面我們提到了建造一個房子,需要業(yè)主,項目負責人,建筑工人。其實建造者模式也包括這三個類:產(chǎn)品類(客戶提出產(chǎn)品需要),指揮者類,建造者類。
建造者模式的使用流程如下:
客戶提出產(chǎn)品需求:比如上面產(chǎn)品就是一輛小汽車,產(chǎn)品要素包括name,number,price,engine
指揮者根據(jù)產(chǎn)品需求,安排建造者完成需求的各個部分
建造者完成相應(yīng)的部分
使用建造者模式修改上面的代碼如下:
//產(chǎn)品類:產(chǎn)品要素 class Car{ constructor(){ this.name = ""; this.number = ""; this.price = ""; this.engine = ""; } } //建造者類:各種工人完成相應(yīng)的部分 class CarBuilder{ setName(){ this.name = "寶馬"; } setNumber(){ this.number = "888888"; } setPrice(price){ this.price = "100萬"; } setEngine(engine){ this.engine = "最好的引擎"; } getCar(){ var car = new Car(); car.name = this.name; car.number = this.number; car.price = this.price; car.engine = this.engine; return car; } } //指揮官類:指揮工人完成各部分工作 class Director{ action(builder){ builder.setName(); builder.setNumber(); builder.setPrice(); builder.setEngine(); } } //使用方法: let builder = new CarBuilder(); let director = new Director(); director.action(builder); let car = builder.getCar(); console.log(car)
從上面的代碼中,我們可以看出,定義了產(chǎn)品類,主要負責定義產(chǎn)品的需求;建造者類,主要負責完成需求的各個部分;指揮者類,主要負責指揮工人完成各部分工作。實際上就是把一輛車的復(fù)雜的創(chuàng)建過程抽離成三個簡單的類來完成,大家各司其職,減少了代碼的耦合。當以后需要添加新的需求時,只需要在各個部分多帶帶定義即可,比如現(xiàn)在造汽車還需要安裝玻璃,那么只需要在每個類里面定義玻璃相關(guān)的要素,建造者,指揮者即可。而不需要考慮代碼的各部分耦合。這就是建造者模式。
原型模式原型模式:通俗點講就是創(chuàng)建一個共享的原型,并通過拷貝這些原型創(chuàng)建新的對象。在我看來,其實原型模式就是指定新創(chuàng)建對象的模型,更通俗一點來說就是我想要新創(chuàng)建的對象的原型是我指定的對象。最簡單的原型模式的實現(xiàn)就是通過Object.create()。Object.create(),會使用現(xiàn)有的對象來提供新創(chuàng)建的對象的__proto__。
let person = { name:"hello", age:24 } let anotherPerson = Object.create(person); console.log(anotherPerson.__proto__) //{name: "hello", age: 24} anotherPerson.name = "world"; //可以修改屬性 anotherPerson.job = "teacher";
上面的代碼使用Object.create()將person對象作為anotherPerson對象的原型,創(chuàng)建了anotherPerson。因此anotherPerson可以直接獲得person的屬性name,age等。
另外,如果我們想要自己實現(xiàn)原型模式,而不是使用封裝好的Object.create()函數(shù),那么可以使用原型繼承來實現(xiàn):
function F(){ } F.prototype.g = function(){} //G類繼承F類 function G(){ F.call(this); } //原型繼承 function Fn(){} Fn.prototype = F.prototype; G.prototype = new Fn(); G.prototype.constructor = G;
上面的代碼,學過js的應(yīng)該都能看懂,沒什么好解釋的。
原型模式總結(jié):
原型模式就是創(chuàng)建一個指定原型的對象。如果我們需要重復(fù)創(chuàng)建某個對象,那么就可以使用原型模式來實現(xiàn)。
上面的創(chuàng)建型設(shè)計模式,專注于創(chuàng)建對象。而結(jié)構(gòu)性設(shè)計模式則專注于結(jié)構(gòu)設(shè)計,通俗點來說就是對象與對象之間的結(jié)構(gòu)設(shè)計,也就是如何將類或者對象進行更好的組織,以方便使用。
外觀設(shè)計模式外觀設(shè)計模式定義:為一組復(fù)雜的子接口提供一個更高級的統(tǒng)一接口,以便更方便的去實現(xiàn)子接口的功能??炊x總是感覺很復(fù)雜,我們根據(jù)一些場景來具體分析:
HTML代碼按鈕1按鈕2js代碼 let oBtn1= document.getElementById("btn1"); let oBtn2= document.getElementById("btn2"); //按鈕1 function btn1Fn(){ console.log("這里是按鈕1的內(nèi)容") } //DOM事件的兼容性處理 if( document.addEventListener ){ oBtn1.addEventListener("click" , btn1Fn, false); }else if(document.attachEvent){ oBtn1.attachEvent("onclick" , btn1Fn); }else{ oBtn1.onclick = btn1Fn; } //按鈕2 function btn2Fn(){ console.log("這里是按鈕2的內(nèi)容") } //DOM事件的兼容性處理 if( document.addEventListener ){ oBtn1.addEventListener("click" , btn2Fn, false); }else if(document.attachEvent){ oBtn1.attachEvent("onclick" , btn2Fn); }else{ oBtn1.onclick = btn2Fn; }
上面代碼中由兩個按鈕,每個按鈕有對應(yīng)的點擊事件。但是我們知道如果直接使用onclickDOM0級點擊事件,那么就可能出現(xiàn)后續(xù)的事件覆蓋前面的。因此建議使用DOM2級點擊事件,但是addEventListener存在兼容性問題,在IE中存在不兼容。因此我們需要對每次事件做兼容性處理。從上面的代碼中,我們可以看出,我們對按鈕1和按鈕2都做了兼容性處理。事實上,這些兼容性處理都是相同的,如果每一次都去寫復(fù)雜的重復(fù)的兼容性代碼,是沒有意義的。因此,我們通常會將兼容性處理封裝起來,作為一個統(tǒng)一的接口,在需要的時候,直接調(diào)用這個接口,而不是再去重復(fù)寫這個復(fù)雜的兼容代碼,這種將復(fù)雜的代碼封裝起來,其實就是外觀設(shè)計模式.我們看使用外觀設(shè)計模式實現(xiàn)的代碼:
let oBtn1= document.getElementById("btn1"); let oBtn2= document.getElementById("btn2"); //按鈕1 function btn1Fn(){ console.log("這里是按鈕1的內(nèi)容") } //按鈕2 function btn2Fn(){ console.log("這里是按鈕2的內(nèi)容") } function bindEvent(element,type,fn) { if(document.addEventListener){ element.addEventListener(type,fn,false) }else if(document.attachEvent){ element.attachEvent("on"+type,fn) }else{ element["on"+type] = fn; } } bindEvent(oBtn1,"click",btn1Fn) bindEvent(oBtn2,"click",btn2Fn)
從上面的代碼中,我們可以看到使用了bindEvent來封裝兼容性處理,然后oBtn1和oBtn2觸發(fā)點擊事件都是直接調(diào)用這個封裝函數(shù)。其實這就是外觀模式的一次典型應(yīng)用。所有的API的兼容性封裝其實都是外觀模式的應(yīng)用。外觀模式其實就是把一些復(fù)雜的操作隱藏起來,然后我們從更高級別直接調(diào)用。我們再看我們在開發(fā)中經(jīng)常使用到的一個例子:
var person = { init:function(){ // 這里是初始化代碼 }, getAge:function(){ // 這里是獲取元素的方法 }, getName:function(){ // 這里是獲取樣式的代碼 }, getJob:function(){ //這里是獲取個人工作的代碼 } } var name = person.getName();
上面的代碼是一個對象person,里面封裝了與個人信息相關(guān)的age,name,job等方法,然后在需要獲取這些信息時,我們使用person.xx來調(diào)用。其實這就是外觀模式的另外一種應(yīng)用。在開發(fā)中,我們經(jīng)常會使用命名空間,將一些相關(guān)的信息都封裝到這個對象中,這種將各類操作饑餓和在一起,對外提供統(tǒng)一的接口就是外觀模式的典型應(yīng)用。
外觀設(shè)計模式總結(jié):外觀設(shè)計模式時結(jié)構(gòu)性設(shè)計模式,我們之前說過結(jié)構(gòu)型設(shè)計模式是用于組織代碼結(jié)構(gòu)的。而外觀設(shè)計模式就是把所有的子類饑餓和在一起,對外提供統(tǒng)一的接口這樣的一種組織形式。典型的使用就是各種兼容性API的封裝和命名空間的使用。
提到適配器,我們想到的可能是電源適配器。一些電子產(chǎn)品的插座是三孔的,但是如果沒有三孔插座,那么我們就需要使用轉(zhuǎn)換器,使用兩孔插座通過轉(zhuǎn)換器來給三孔電子產(chǎn)品充電,這個轉(zhuǎn)換器就是適配器。在生活中,我們經(jīng)常會碰到這種由于接口不同需要通過轉(zhuǎn)換來實現(xiàn)的情況,在實際的代碼開發(fā)中,我們經(jīng)常也會遇到接口不同,需要進行轉(zhuǎn)換的過程。這個轉(zhuǎn)換的過程就是適配。這種設(shè)計模式就是適配器設(shè)計模式。
適配器模式定義:將一個類的接口轉(zhuǎn)換成另外一個接口,以滿足用戶需求,解決接口不一樣而產(chǎn)生的問題??淳唧w的代碼:
function getInfo(){ //假設(shè)result為請求后臺得到的數(shù)據(jù),數(shù)組形式 var result = ["henry",24,"teacher"]; console.log("名字是"+result[0]+"年齡是"+result[1]+"職業(yè)是"+result[2]) }
上面的代碼中,定義了一個用于獲取個人信息的函數(shù),個人信息的獲取為通過后臺接口返回到result中?,F(xiàn)在返回的數(shù)據(jù)為數(shù)組形式。我們后面所有的操作都是按照數(shù)組來進行的,比如獲取姓名result[0]。獲取年齡為result[1]。但是,假如后端接口發(fā)生變化了,不再返回數(shù)組形式了(這在實際的開發(fā)中可能非常常見)。而實返回一個對象了。那么我們后面所有的使用數(shù)組的操作就都是錯誤的了,這樣的話所有涉及到數(shù)據(jù)的都需要進行修改,如果后續(xù)代碼非常多,那么修改起來就非常麻煩了。這時候,我們可能考慮變通一下,將返回的對象,轉(zhuǎn)換成我們需要的數(shù)組即可。代碼如下:
function fn2(){ //假設(shè)result2為請求后臺得到的數(shù)據(jù),對象形式 var result2 = { name:"henry", age:24, job:"teacher" } //將對象轉(zhuǎn)化成數(shù)組 function objToArray(obj){ var arr = []; for(var key in obj){ arr.push(obj[key]) } return arr; } var result = objToArray(result2) console.log("名字是"+result[0]+"年齡是"+result[1]+"職業(yè)是"+result[2]) }
上面的代碼中,result2為后臺請求的接口,數(shù)據(jù)類型是對象。但是我們之前都是按照數(shù)組處理的,因此我們需要將對象轉(zhuǎn)換成數(shù)組,函數(shù)objToArray就是用來轉(zhuǎn)換的函數(shù)。這樣的話,我們就不需要去修改后面的代碼,這個轉(zhuǎn)換的過程就是適配過程。objToArray就是適配函數(shù)。這就是適配器設(shè)計模式。
適配器設(shè)計模式總結(jié):適配器設(shè)計模式其實就是把一些不適合我們當前使用的接口,通過適配以后,轉(zhuǎn)換成能夠被我們使用的接口。最常見的就是接口的轉(zhuǎn)換
提到代理,我們可能想到的是代理人。在生活中代理人最多的可能就是明星了,每個明星都有自己的代理人,我們找明星合作通常都是先接觸代理人,然后才接觸明星。也就是說這個代理人是在我們和明星之間進行了一次攔截,篩選出符合見明星的人。其實代理模式在開發(fā)中也是這樣,只不過這里代理的是對象,而不是明星,通過為對象提供一個代理,用來控制對這個對象的訪問。
代理模式定義:為對象提供一個代理,用來控制對這個對象的訪問。
下面以具體代碼舉例:比如公司的個人信息的訪問:
let person = { id = "1", name = "劉亦菲", age:30 } console.log(person.name)
上面定義了一個包含個人信息的對象person,如果沒有進行代理,那么可以直接通過person.xx進行訪問。但是,事實上我們不希望個人信息被查看,只有本人能夠進行查看和修改。那么這時候我們可以對對象的訪問添加代理。具體代碼如下:
let info = (function(){ let person = { id:1, name:"劉亦菲", age:24, job:"teacher" } return function({id}){ //代理對象 let handle = { get:function(target,key){ if(key === "age"){ if(id === 1){ return Reflect.get(target,key) }else{ throw new Error("您沒有權(quán)限查看該信息") } } }, set:function(target,key,value){ if(id === 1){ return Reflect.set(target,key,value) }else{ throw new Error("您沒有權(quán)限修改個人信息") } } } return new Proxy(person,handle) } })() let star = info({id:1}) console.log(star.age) star.age = 30; console.log(star.age)
通過使用new Proxy(target,handle)來進行代理。其中target為被代理對象,handle為代理對象或者說攔截對象。在handle中我們通過定義get和set函數(shù)來進行攔截,只有id為1的人才能查看自己的個人信息。這就是代理模式。
代理模式的應(yīng)用場景:
上面的例子只是簡單的代理對象的訪問,其實代理更多的時候是用于控制開銷很大的對象的訪問。比如,一個創(chuàng)建實例開銷很大的訪問,它會把創(chuàng)建實例放到方法被調(diào)用的時候(也就是真正需要被用到的時候),因為如果整個程序運行期間都沒有用到這個對象,那么就不需要創(chuàng)建它,這樣可以大大節(jié)省資源。比如圖片的延遲加載,我們并不需要一開始就加載所有的圖片,而是使用一張loading圖片來代替它們,只有在真正需要展示這張圖片的時候,再替代掉src即可。
代理模式的總結(jié):
歸功接底代理模式就是控制對對象的訪問,無論是攔截訪問,還是延遲訪問都是對對象訪問的控制罷了。如果你要訪問一個對象,但是你不想馬上訪問,或者不想直接訪問那么這都是代理模式。
裝飾是生活中很常見的行為,我們給房子搞裝修,自己裝扮自己的臥室等這些都能夠算作裝飾,那么我們?yōu)槭裁匆阊b飾?還不是為了讓房子變得更加美觀,漂亮,更有特色。同樣,在開發(fā)中裝飾者模式也是為了給對象增加新的特性,或者說增加新的功能。
裝飾者模式定義:在不創(chuàng)建新對象的情況下,給對象添加新的特性。就類似于在不破壞房子的情況下,給房子進行裝修,我們不可能把買來的房子拆了再重新建一個再裝修,同樣裝飾者模式是在不創(chuàng)建新對象的情況下,給對象增加新的特性。下面看具體代碼:
class Car{ constructor(name,price){ this.name = name, this.price = price } getCar(){ console.log("買了這輛車") } } let xiaoming = new Car("寶馬","100萬"); xiaoming.getCar(); let xiaohong = new Car("豐田","50萬"); xiaohong.getCar(); let xiaogang = new Car("大眾","30萬"); xiaogang.getCar();
如上面代碼所示:定義了汽車類,小明花了100萬買了寶馬車,小紅花了50萬買了豐田車,小剛花了30萬買了大眾車。但是這時候經(jīng)銷商突然進行促銷了,說買寶馬的可以再送兩個輪胎,買豐田的可以再送購物卡,買大眾的可以再送加油卡。那么這時候?qū)τ谛∶?,小剛,小紅它們來說應(yīng)該怎么辦了?他們應(yīng)該也有這些送的東西(注意每種車送的東西不一樣),我們不可能再將這些特性再添加到Car類上面去,然后再創(chuàng)建小明等實例去買車。也就是說我們需要再不創(chuàng)建新的實例的情況下,給對象添加特性,這恰好是裝飾著模式的定義,下面看具體的代碼實現(xiàn):
class Car{ constructor(name,price){ this.name = name, this.price = price } getCar(){ console.log("買了這輛車") } } let xiaoming = new Car("寶馬","100萬"); xiaoming.getCar(); let xiaohong = new Car("豐田","50萬"); xiaohong.getCar(); let xiaogang = new Car("大眾","30萬"); xiaogang.getCar(); //裝飾過程 function decoratorBaoma(){ this.wheel = "寶馬車送輪胎" } decoratorBaoma.call(xiaoming) function decoratorFengtian(){ this.shoppingcard = "豐田車送購物卡" } decoratorFengtian.call(xiaohong) function decoratordazhong(){ this.oil = "大眾車送加油卡" } decoratordazhong.call(xiaogang) console.log(xiaoming) console.log(xiaohong)
如面代碼所示:我們在沒有創(chuàng)建新對象的情況下,定義了三個裝飾函數(shù),通過給寶馬車用戶添加wheel屬性送輪胎,給豐田車用戶添加shoppingcard屬性送購物卡,給大眾車用戶添加oil屬性送加油卡。然后執(zhí)行這幾個函數(shù),修改相對應(yīng)的this指向即可。這樣的話就實現(xiàn)了我們想要的功能。這就是裝飾者模式。
裝飾者模式總結(jié)裝飾者模式就是在不創(chuàng)建新對象的情況下,給對象添加新的特性。
橋梁的作用主要就是連接岸的兩邊。在開發(fā)中,橋接模式的作用也是用于連接,只不過它連接的是抽象部分和實現(xiàn)部分。這么說可能很抽象,我們用具體的例子來展示。橋接模式在javascript中應(yīng)用最廣泛的就是事件監(jiān)聽。
box1var oBox1 = document.getElementById("box1"); bindEvent(oBox1,"click",getBeerById) function getBeerById(){ var id = this.id; asyncRequest("GET","beer.uri?id="+id,function(resp) { console.log(resp.responseText); }) }
如上面代碼所示,我們定義了一個div,給id為box1的div綁定了點擊事件,事件函數(shù)為getBeerById。其中bindEvend點擊事件為抽象類,getBeerById為具體實現(xiàn)類。我們可以發(fā)現(xiàn)這個就實現(xiàn)類,也就是getBeerById函數(shù),只能作為事件觸發(fā)函數(shù),因為它里面的this.id依賴于點擊事件即抽象類。我們沒辦法對getBeerById進行單元測試?,F(xiàn)在假設(shè)我們又新增了一個div,它根據(jù)類名傳遞參數(shù),代碼如下所示:
box1var oBox2 = document.getElementsByClassName("box2")[0]; bindEvent(oBox2,"click",getBeerByClassName) function getBeerByClassName(){ var className = this.className; asyncRequest("GET","beer.uri?id="+className,function(resp) { console.log(resp.responseText); }) }
上面這個事件處理函數(shù)getBeerByClassName,不再是根據(jù)id請求數(shù)據(jù)了,而是根據(jù)className請求數(shù)據(jù)。他們之間僅僅是傳遞參數(shù)的區(qū)別,根據(jù)代碼復(fù)用和抽象的原則,我們可能會將代碼公告部分抽離出來:
function getBeerId(id){ asyncRequest("GET","beer.uri?id="+id,function(resp) { console.log(resp.responseText); }) }
將代碼公共部分抽離出來后,雖然提高了代碼的服用。但是帶來了新的問題,由于之前的具體實現(xiàn)是作為事件處理函數(shù),它依賴于抽象類即點擊事件。抽象后的代碼沒辦法作為事件處理函數(shù)了(因為之前通過this獲取id和className,現(xiàn)在是將其作為參數(shù)進行封裝了),因此為了保證原來的功能,我們需要對代碼及性能修改,使用新的事件處理函數(shù)。
//抽象類 bindEvent(oBox1,"click",getBeerByIdBridge) //橋接函數(shù) function getBeerByIdBridge(e){ getBeerById(this.id) } //具體類 function getBeerById(id){ asyncRequest("GET","beer.uri?id="+id,function(resp) { console.log(resp.responseText) }) }
通過上面的代碼,我們可以知道,定義了一個函數(shù)getBeerByIdBridge來作為事件處理函數(shù)。但是這個函數(shù)并沒有實現(xiàn)具體的功能。具體功能的實現(xiàn)在getBeerById中,也就是說這個函數(shù)其實只是點擊事件和具體功能之間連接的橋梁。這就是橋接模式,這個函數(shù)就是橋接函數(shù)。通過上面的代碼,我們可以知道橋接模式就是抽象類和具體類之間抽離開來,使得他們能夠各自獨自變化,而不是互相依賴。
橋接模式的另一個應(yīng)用:用于連接多個類。
橋接模式不僅能夠用來連接抽象和具體實現(xiàn),而且還能夠用于連接多個類,這些多各類實現(xiàn)各部分功能,通過橋接模式實現(xiàn)完整的功能??淳唧w的代碼如下:
//A類 class A { constructor(name,age){ this.name = name; this.age = age; } showName(){ console.log(this.name) } } //B類 class B { constructor(job,sex){ this.job = job; this.sex = sex; } showJob(){ console.log(this.job) } } //橋接類 class Bridge{ constructor(){ this.w = new A("劉亦菲",30); this.h = new B("actor","女") } } let bridge = new Bridge(); console.log(bridge) bridge.w.showName()
如上面代碼所示,通過橋接類將類A和類B連接起來了,A類用于記錄姓名和年齡,B類用于記錄職業(yè)和性別,橋接類用于這些功能的所有的實現(xiàn),相當于記錄一個人的完整信息。這樣的話我們進行開發(fā)時,可以分別對A類和B類進行多帶帶開發(fā),各部分實現(xiàn)各自的功能,最后再通過橋接類實現(xiàn)完整功能。
橋接模式的總結(jié):橋接模式用于將抽象與其是實現(xiàn)隔離開來,以便二者獨立變化。這種模式對于事件驅(qū)動的編程非常方便。另外橋接模式還可以用于將多個類連接起來,用于實現(xiàn)一個完整功能。
組合模式組合:是指把一些零散的東西匯聚成一個整體,或者說把部分匯聚成整體。在開發(fā)中,組合模式同樣如此。
組合模式的定義:組合模式又稱部分-整體模式,將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu),組合模式使得用戶對單個對象和組合對象的使用具有一致性。從組合模式的定義我們可以知道,組合模式有兩個特點:
1.組合模式是部分與整體的層次關(guān)系,形成樹形結(jié)構(gòu)
組合模式的層次關(guān)系如下如圖所示:
從圖中我們可以看到組合模式的整個結(jié)構(gòu)是:一些葉子對象(也就是部分)組合成一級組合對象,這些組合對象又組合成一個組合對象,最終形成這種樹形結(jié)構(gòu)。我們以一個生活中實際的例子來舉例:
圖片描述
比如我們點餐,首先是整個菜單,然后菜單分為三類:主菜,飲料和甜品。每一類下面又進行劃分,比如主菜包括土豆絲,西紅柿炒雞蛋,紅燒牛肉等。這樣形成了一個屬性菜單。
另外,提到樹形結(jié)構(gòu),我們學習的DOM樹就是最常見的屬性結(jié)構(gòu)。
2.組合模式使得用戶對單個對象和組合對象具有一致性。這句話怎么理解了,其實就是組合獨享和單個對象都具有一些相同的API(可以這么粗暴的理解),比如都定義成同名函數(shù)。
比如,我們以jQuery操作DOM為例。
如上面代碼所示:我們知道DOM結(jié)構(gòu)是樹形結(jié)構(gòu),同時使用jQuery時,我們既可以對div使用css方法,又可以對div的子元素使用css方法,也就是說樹型結(jié)構(gòu)的組合和整體對象都能夠使用相同的css方法。同樣我們再以剛才的訂餐舉例:
從上面的圖中我們可以看出,所有的對象無論是葉子對象還是組合對象都具有相同的add方法,也就是說這個結(jié)構(gòu)的組合對象的API使用具有一致性。
組合模式總結(jié):組合模式牢記兩個特點:
組合對象之間能夠形成樹形結(jié)構(gòu)
組合對象之間的API使用具有一致性。所謂一致性就是類似于同名函數(shù)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/100932.html
摘要:函數(shù)式編程前端掘金引言面向?qū)ο缶幊桃恢币詠矶际侵械闹鲗?dǎo)范式。函數(shù)式編程是一種強調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向?qū)ο缶幊桃恢币詠矶际荍avaScript中的主導(dǎo)范式。JavaScript作為一門多范式編程語言,然而,近幾年,函數(shù)式編程越來越多得受到開發(fā)者的青睞。函數(shù)式編程是一種強調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。因此,...
摘要:插件開發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強大而且簡單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實現(xiàn)文件分片斷點續(xù)傳。 Vue.js 插件開發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強大而且簡單的方式。插....
摘要:年,和前端開發(fā)者與應(yīng)用程序前端開發(fā)者之間產(chǎn)生了巨大的分歧。開發(fā)最常見的解決方案有手機和平板的原生應(yīng)用程序桌面應(yīng)用程序桌面應(yīng)用程序原生技術(shù)最后,前端開發(fā)者可以從瀏覽器開發(fā)中學習到,編寫代碼不需要考慮瀏覽器引擎的限制。 前端開發(fā)者手冊2019 Cody Lindley 編著 原文地址 本手冊由Frontend Masters贊助,通過深入現(xiàn)代化的前端工程課程來提高你的技能。 下載:PDF ...
摘要:年,和前端開發(fā)者與應(yīng)用程序前端開發(fā)者之間產(chǎn)生了巨大的分歧。開發(fā)最常見的解決方案有手機和平板的原生應(yīng)用程序桌面應(yīng)用程序桌面應(yīng)用程序原生技術(shù)最后,前端開發(fā)者可以從瀏覽器開發(fā)中學習到,編寫代碼不需要考慮瀏覽器引擎的限制。 前端開發(fā)者手冊2019 Cody Lindley 編著 原文地址 本手冊由Frontend Masters贊助,通過深入現(xiàn)代化的前端工程課程來提高你的技能。 下載:PDF ...
摘要:年,和前端開發(fā)者與應(yīng)用程序前端開發(fā)者之間產(chǎn)生了巨大的分歧。開發(fā)最常見的解決方案有手機和平板的原生應(yīng)用程序桌面應(yīng)用程序桌面應(yīng)用程序原生技術(shù)最后,前端開發(fā)者可以從瀏覽器開發(fā)中學習到,編寫代碼不需要考慮瀏覽器引擎的限制。 前端開發(fā)者手冊2019 Cody Lindley 編著 原文地址 本手冊由Frontend Masters贊助,通過深入現(xiàn)代化的前端工程課程來提高你的技能。 下載:PDF ...
閱讀 1705·2021-11-16 11:44
閱讀 2436·2021-10-11 11:07
閱讀 4138·2021-10-09 09:41
閱讀 696·2021-09-22 15:52
閱讀 3224·2021-09-09 09:33
閱讀 2770·2019-08-30 15:55
閱讀 2307·2019-08-30 15:55
閱讀 865·2019-08-30 15:55