文章初衷
設計模式其實旨在解決語言本身存在的缺陷,
目前javaScript一些新的語法特性已經整合了一些設計模式的實現,
大家在寫程式碼的時候,沒必要為了用設計模式而去用設計模式,
那麼我這邊為什麼還寫設計模式的文章呢,
一方面是自己的一個整理,然後記錄出來,結合自己的理解,
一方面就是雖然語言特性本身已經實現這些模式,有了自己的語法,
但是我們何嘗不能去了解一下它是通過什麼樣的思路去實現了
在我看來設計模式更多的是讓我對於思考問題,有了一些更好的思路和想法
文章實現更多的表現為用一些簡單的案例,幫助大家去理解這樣的一種思路,
會存在故意把設計模式的實現往簡單的案例靠攏,
大家在真實專案中不要刻意去用設計模式實現相同的程式碼
設計模式在平時的一些程式碼中都會有所體現,大家也許經常用到,
耐心看文章,也許你會發現自己平時的程式碼就不斷在設計模式中體現
JavaScript設計模式系列
JavaScript設計模式系列,講述大概20-30種設計模式在JavaScript中的運用
後面對應的篇幅會陸續更新,歡迎大家提出建議
這是設計模式系列第一篇,講述工廠模式
注意
深入系列文章部分是有先後順序的,按照目錄結構順序閱讀效果最好。
勘誤及提問
如果有疑問或者發現錯誤,可以在相應的 issues 進行提問或勘誤。
如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
工廠模式
首先需要說一下工廠模式。工廠模式根據抽象程度的不同分為三種
- 簡單工廠模式
- 工廠方法模式
- 抽象工廠模式
1.簡單工廠模式
簡單工廠模式:又稱為靜態工廠方法模式,它屬於類建立型模式。
在簡單工廠模式中,可以根據引數的不同返回不同類的例項。
由工廠物件決定建立某一種產品物件類的例項。
上?
// #簡單工廠模式第一種
/**
* 足球類
*/
var FootBall = function () {
this.play = function () {
console.log('我在踢足球');
}
}
/**
* 籃球類
*/
var BasketBall = function () {
this.play = function () {
console.log('我在打籃球');
}
}
var football = new FootBall();
football.play();
var basketball = new BasketBall();
basketball.play();
/**
* 球類工廠
*/
var Ball = function(name) {
switch (name) {
case '足球':
return new FootBall();
break;
case '籃球':
return new BasketBall();
break;
}
}
var football = Ball('足球');
football.play();
var basketball = Ball('籃球');
basketball.play();
// #簡單工廠模式第一種end複製程式碼
這段案例可以簡單的這麼去理解,假設我們需要多個球,我們希望在使用球
的時候,只需要告訴管理員我們需要的球的型別,不需要一個個去找對應的球
這個管理員就相對於工廠函式。
簡單講就是使用簡單工廠模式,那麼你就不需要關心它的具體實現,
你只需要知道你要使用的型別,那麼工廠函式會自動幫你去做對應的事情
再看一個?
// #簡單工廠模式第二種
/**
* 球類工廠
*/
var Ball = function(name) {
// 建立一個物件,對物件擴充套件擴充套件屬性還有方法
var o = new Object();
o.name = name;
//預設的方法 如果在加上一個羽毛球類,這時候就不需要補充play方法
o.play = function () {
console.log('我在打'+name);
}
if (name === '足球') {
o.play = function () {
console.log('我在踢'+name);
}
}else if (name === '籃球') {
o.play = function () {
console.log('我在打'+name);
}
}
// 將物件返回
return o;
}
var football = Ball('足球');
football.play();
var basketball = Ball('籃球');
basketball.play();
// #簡單工廠模式第二種end複製程式碼
這段案例是用物件的方式代替多個類,把相同點抽離出來,
不同點在一一做型別判斷,這樣也是簡單工廠模式實現的另一種方式
簡單工廠模式的優點:
工廠類含有必要的判斷邏輯,可以決定在什麼時候建立哪一個產品類的例項,客戶端可以免除直接建立產品物件的責任,而僅僅“消費”產品;簡單工廠模式通過這種做法實現了對責任的分割,它提供了專門的工廠類用於建立物件
客戶端無須知道所建立的具體產品類的類名,只需要知道具體產品類所對應的引數即可,對於一些複雜的類名,通過簡單工廠模式可以減少使用者的記憶量。
簡單工廠模式的缺點
- 由於工廠類集中了所有產品建立邏輯,一旦不能正常工作,整個系統都要受到影響。
- 使用簡單工廠模式將會增加系統中類的個數,在一定程式上增加了系統的複雜度和理解難度。
- 系統擴充套件困難,一旦新增新產品就不得不修改工廠邏輯,在產品型別較多時,有可能造成工廠邏輯過於複雜,不利於系統的擴充套件和維護。
簡單工廠模式的適用情況
在以下情況下可以使用簡單工廠模式:
- 工廠類負責建立的物件比較少:由於建立的物件較少,不會造成工廠方法中的業務邏輯太過複雜。
- 客戶端只知道傳入工廠類的引數,對於如何建立物件不關心:客戶端既不需要關心建立細節,甚至連類名都不需要記住,只需要知道型別所對應的引數。
簡單工廠模式總結
- 簡單工廠模式的要點在於:當你需要什麼,只需要傳入一個正確的引數,就可以獲取你所需要的物件,而無須知道其建立細節。
- 簡單工廠模式最大的優點在於實現物件的建立和物件的使用分離,將物件的建立交給專門的工廠類負責,但是其最大的缺點在於工廠類不夠靈活,增加新的具體產品需要修改工廠類的判斷邏輯程式碼,而且產品較多時,工廠方法程式碼將會非常複雜。
2.工廠方法模式
工廠方法模式:又稱為工廠模式,也叫虛擬構造器模式或者多型工廠模式
它屬於類建立型模式。在工廠方法模式中,工廠父類負責定義建立產品對
象的公共介面,而工廠子類則負責生成具體的產品物件,
這樣做的目的是將產品類的例項化操作延遲到工廠子類中完成,
即通過工廠子類來確定究竟應該例項化哪一個具體產品類
這樣解釋可能會有點抽象,不急,下面來看一段?
// # 工廠方法模式
// 安全模式建立工廠類
var Ball = function (type,name) {
/**
* 安全模式 Ball也可以執行處new Ball的效果
*/
if(this instanceof Ball) {
var s = new this[type](name);
return s;
}else {
return new Ball(type,name);
}
}
// 工廠原型中設定建立所有型別資料物件的基類
Ball.prototype = {
basketBall: function(name) {
this.play = function() {
console.log('我在打'+name);
}
},
footBall: function(name) {
this.play = function() {
console.log('我在踢'+name);
}
},
badmintonBall: function(name) {
this.play = function() {
console.log('我在打'+name);
}
},
// ....
}
var football = new Ball('footBall','足球');
football.play();
var basketball = new Ball('basketBall','籃球');
basketball.play();
var badmintonball = new Ball('badmintonBall','羽毛球');
badmintonball.play();
// # 工廠方法模式end複製程式碼
這段案例是這麼去理解的,我們先建立一個球類工廠,
這個球類工廠是一個抽象的,不做具體的實現,然後我們在這個球類工廠裡
面在去定義對應的球類實現,如籃球,羽毛球,足球等實現,
在工廠方法模式中,抽象類工廠只是負責定義一個對外的公共介面,
而工廠子類則負責生成具體的產品物件。
這樣做的目的是將產品類的例項化操作延遲到工廠子類中完成,即通過
工廠子類來確定究竟應該例項化哪一個具體產品類
如果這時候在出現一個新的球類運動,只需要為這種新型別的球類建立
一個具體的球類實現就可以,這一特點無疑使得工廠方法模式具有
超越簡單工廠模式的優越性,更加符合“開閉原則”
上面案例包含了一個安全模式的知識點
// 安全模式建立工廠類
var Ball = function (type,name) {
/**
* 安全模式 Ball也可以執行處new Ball的效果
*/
if(this instanceof Ball) {
var s = new this[type](name);
return s;
}else {
return new Ball(type,name);
}
}複製程式碼
這段程式碼主要解決的問題是,有些同學使用工廠類的時候,
忘記使用關鍵字new,得不到預期想要的效果
這邊的解決方案就是,在建構函式開始時先判斷當前物件this指代
是不是當前工廠類,如果不是則通過new關鍵字建立物件返回,
這樣就可以實現不使用new關鍵詞也可以達到相同的效果了
工廠方法模式的優點:
- 在工廠方法模式中,工廠方法用來建立客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被例項化這一細節,使用者只需要關心所需產品對應的工廠,無須關心建立細節,甚至無須知道具體產品類的類名。
- 基於工廠角色和產品角色的多型性設計是工廠方法模式的關鍵。它能夠使工廠可以自主確定建立何種產品物件,而如何建立這個物件的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱為多型工廠模式,是因為所有的具體工廠類都具有同一抽象父類。
- 使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的介面,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要新增一個具體工廠和具體產品就可以了。這樣,系統的可擴充套件性也就變得非常好,完全符合“開閉原則”。
工廠方法模式的缺點:
- 在新增新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度,有更多的類需要編譯和執行,會給系統帶來一些額外的開銷。
- 由於考慮到系統的可擴充套件性,需要引入抽象層,在客戶端程式碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度
工廠方法模式的適用情況
在以下情況下可以使用工廠方法模式:
- 一個類不知道它所需要的物件的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品物件由具體工廠類建立;客戶端需要知道建立具體產品的工廠類。
- 一個類通過其子類來指定建立哪個物件:在工廠方法模式中,對於抽象工廠類只需要提供一個建立產品的介面,而由其子類來確定具體要建立的物件,利用物件導向的多型性和里氏代換原則,在程式執行時,子類物件將覆蓋父類物件,從而使得系統更容易擴充套件。
- 將建立物件的任務委託給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類建立產品子類,需要時再動態指定,可將具體工廠類的類名儲存在配置檔案或資料庫中。
工廠方法模式總結
- 工廠方法模式是簡單工廠模式的進一步抽象和推廣。由於使用了物件導向的多型性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。在工廠方法模式中,核心的工廠類不再負責所有產品的建立,而是將具體建立工作交給子類去做。這個核心類僅僅負責給出具體工廠必須實現的介面,而不負責產品類被例項化這種細節,這使得工廠方法模式可以允許系統在不修改工廠角色的情況下引進新產品。
- 工廠方法模式的主要優點是增加新的產品類時無須修改現有系統,並封裝了產品物件的建立細節,系統具有良好的靈活性和可擴充套件性;其缺點在於增加新產品的同時需要增加新的工廠,導致系統類的個數成對增加,在一定程度上增加了系統的複雜性。
3.抽象工廠模式
抽象工廠模式:通過對類的工廠抽象使其業務用於對產品類簇的建立,
而不是負責建立某一類產品的例項,屬於物件建立型模式。
抽象類一直出現在我們的文章中,那麼這邊先來解釋一下什麼是抽象類
抽象類是一種宣告但不能使用的類,當你使用時就會報錯,
在JavaScript中abstract還是一個保留字,所以目前來說還不能像
傳統面嚮物件語言那麼輕鬆的去建立抽象類.
不過JavaScript有自己的實現方式,可以模擬出抽象類
來一段程式碼
// 抽象類的介紹
var Ball = function () {}
Ball.prototype = {
play: function () {
return new Error('抽象方法不能呼叫');
}
}複製程式碼
解釋:
我們可以看到建立的Ball類其實什麼都不能做,建立時沒有任何屬性,
原型定義的方法也不能使用,否則就會報錯。但是在繼承上卻是很有用的,
因為定義了一種類,並定義了該類所具備的方法,
如果沒有在子類中重寫這寫方法,那麼呼叫的時候就會報錯。
這一特點可以很好的提醒子類去重寫這一方法,不然會在呼叫的時候提示錯誤
那麼在瞭解了什麼是抽象類的情況下,我們在來比較一下工廠方法模式與抽象工廠模式的不同點,以方便我們更好的去理解抽象工廠模式
工廠方法模式與抽象工廠模式的對比
在工廠方法模式中具體工廠負責生產具體的產品,每一個具體工廠對應一種具體產品,工廠方法也具有唯一性,一般情況下,一個具體工廠中只有一個工廠方法或者一組過載的工廠方法。但是有時候我們需要一個工廠可以提供多個產品物件,而不是單一的產品物件。
為了更清晰地理解抽象工廠模式,需要先引入兩個概念:
- 產品等級結構 :產品等級結構即產品的繼承結構,
如一個抽象類是電視機,其子類有海爾電視機、海信電視機、
TCL電視機,則抽象電視機與具體品牌的電視機之間構成了
一個產品等級結構,抽象電視機是父類,而具體品牌的電視機是其子類。
- 產品族 :在抽象工廠模式中,產品族是指由同一個工廠生產的,
位於不同產品等級結構中的一組產品,如海爾電器工廠生產的
海爾電視機、海爾電冰箱,海爾電視機位於電視機產品等級結構中,
海爾電冰箱位於電冰箱產品等級結構中。
抽象工廠模式與工廠方法模式最大的區別在於,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則需要面對多個產品等級結構,一個工廠等級結構可以負責多個不同產品等級結構中的產品物件的建立 。當一個工廠等級結構可以建立出分屬於不同產品等級結構的一個產品族中的所有物件時,抽象工廠模式比工廠方法模式更為簡單、有效率。
這句話比較簡單的理解方式就是:如果一個工廠只需要生產一個型別的
產品比如說電視機,那麼用工廠方法模式是比較合理的,
如果這個工廠又需要成產電視機,又需要生產冰箱之類的,
那麼這時候用工廠抽象模式就是最合適的。
上?,再解釋
// # 抽象工廠模式
var Sport = function(subType, superType) {
if( typeof Sport[superType] === 'function'){
// 快取類
function F() {};
// 繼承父類屬性和方法
F.prototype = new Sport[superType]();
// 將子類constructor 指向子類
subType.constructor = subType;
// 子類原型繼承 “父類”
subType.prototype = new F();
}else {
// 不存在抽象類則丟擲錯誤
throw new Error('未建立該抽象類');
}
}
// 球類運動抽象類
Sport.Ball = function () {
this.type = 'ball';
}
Sport.Ball.prototype = {
play: function () {
return new Error('抽象方法不能呼叫');
}
}
// 力量型運動抽象類
Sport.Power = function () {
this.type = 'power';
}
Sport.Power.prototype = {
play: function () {
return new Error('抽象方法不能呼叫');
}
}
// 速度型運動抽象類
Sport.Speed = function () {
this.type = 'speed';
}
Sport.Speed.prototype = {
play: function () {
return new Error('抽象方法不能呼叫');
}
}
// 籃球類
var BasketBall = function (name) {
this.name = name;
};
// 抽象工廠實現對球類運動的繼承
Sport(BasketBall,'Ball');
BasketBall.prototype.play = function () {
console.log('我在玩'+this.name);
}
// 舉重類
var WeightLifting = function (name) {
this.name = name;
};
// 抽象工廠實現對力量型運動的繼承
Sport(WeightLifting,'Power');
WeightLifting.prototype.play = function () {
console.log('我在玩'+this.name);
}
// 跑步類
var Running = function (name) {
this.name = name;
};
// 抽象工廠實現對速度運動的繼承
Sport(Running,'Speed');
Running.prototype.play = function () {
console.log('我在'+this.name);
}
// 抽象工廠模式實現
var basketBall = new BasketBall('籃球');
console.log(basketBall.type);//ball
basketBall.play();
var weightLifting = new WeightLifting('舉重');
console.log(weightLifting.type);//power
weightLifting.play();
var running = new Running('跑步');
console.log(running.type);//ball
running.play();
/** 輸出結果
* ball
* 我在玩籃球
* power
* 我在玩舉重
* speed
* 我在跑步
*/
// # 抽象工廠模式end複製程式碼
這段栗子先是建立一個運動類的抽象工廠,通過這個暴露外部呼叫的介面,
傳遞2個引數,一個是subType,當前例項化的物件,也就是子類,
一個是superType,需要繼承的父類(抽象類)的名稱,
在工廠函式中實現了子類對父類的繼承。
在繼承過程中有一個地方需要注意,就是在對過渡類繼承的時候,
我們不是繼承父類原型,而是通過new關鍵字複製父類的一個實列,
這樣做的目的是過渡類不僅僅繼承父類的原型方法,還需要繼承
父類的物件屬性,所以通過new關鍵字的方式實現了繼承。
然後通過在抽象工廠類上面進行擴充套件對應的抽象類,
也就是我們需要通過繼承的父類,我這邊新增了3個抽象類
Ball,Power,Speed,分別給抽象類指定了type屬性,還有方法play,
既然建立了抽象類,那麼下面就是開始使用抽象工廠去建立子類,
這邊分別對Ball,Power,Speed 3個抽象類進行了實現
建立了BasketBall(球類),WeightLifting(力量),Running(速度)3個子類
3個子類分別對play方法進行了實現
最後就是對於子類的呼叫實現,在實現子類呼叫的時候,
我們可以獲取到繼承的父類當中對應的type
抽象工廠模式的優點:
當一個產品族中的多個物件被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的物件。
增加新的具體工廠和產品族很方便,無須修改已有系統,符合“開閉原則”。
抽象工廠模式的缺點:
開閉原則的傾斜性(增加新的工廠和產品族容易,增加新的產品等級結構麻煩)。
增加新的產品等級結構:對於增加新的產品等級結構,需要修改所有的工廠角色,包括抽象工廠類,在所有的工廠類中都需要增加生產新產品的方法,不能很好地支援“開閉原則”。
抽象工廠模式的適用情況:
在以下情況下可以使用抽象工廠模式:
一個系統不應當依賴於產品類例項如何被建立、組合和表達的細節,這對於所有型別的工廠模式都是重要的。
系統中有多於一個的產品族,而每次只使用其中某一產品族。
屬於同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。系統提供一個產品類的庫,所有的產品以同樣的介面出現,從而使客戶端不依賴於具體實現。
抽象工廠模式總結
抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態。抽象工廠模式與工廠方法模式最大的區別在於,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則需要面對多個產品等級結構。
抽象工廠模式適用情況包括:一個系統不應當依賴於產品類例項如何被建立、組合和表達的細節;系統中有多於一個的產品族,而每次只使用其中某一產品族;屬於同一個產品族的產品將在一起使用;系統提供一個產品類的庫,所有的產品以同樣的介面出現,從而使客戶端不依賴於具體實現。
4.三大工廠模式的關聯性
當抽象工廠模式中每一個具體工廠類只建立一個產品物件,
也就是隻存在一個產品等級結構時,抽象工廠模式轉換成工廠方法模式;
當工廠方法模式中抽象工廠與具體工廠合併,提供一個統一的工廠
來建立產品物件,並將建立物件的工廠方法設計為靜態方法時,
工廠方法模式退化成簡單工廠模式。
注:根據實際適用情況去選擇對應的工廠模式
注意
深入系列文章部分是有先後順序的,按照目錄結構順序閱讀效果最好。