一個類或對象中每每會包含別的對象。在建立這種成員對象時,你可能習慣於使用常規方式,也即用new關鍵字和類構造函數。問題在於這回致使相關的兩個類之間產生依賴性。javascript
工廠模式用於消除這兩個類之間的依賴性,它使用一個方法來決定究竟要實例化哪一個具體的類。這裏介紹簡單工廠模式(動態選擇並生成實例)及真正的工廠模式(亦稱工廠方法),後者使一個類的實例化延遲到了子類。而子類能夠重寫接口方法以便建立的時候指定本身的對象類型。html
1、簡單工廠java
拿生產自行車舉例子,先看它的類及對應的方法——設計模式
var Bicycle = function() {}; Bicycle.prototype = { // 組裝 assemble: function() { }, // 清洗 wash: function() { }, // 騎車 ride: function() { }, // 維修 repair: function() { } };
假設你想開幾個自行車商店,每一個店都有集中型號的自行車出售。這能夠用一個類來表示——ide
var BicycleShop = function() {}; BicycleShop.prototype = { // 賣自行車 sellBicycle: function(model) { var bicycle; // 注:默認這裏的每一款自行車都繼承Bicycle,且都有本身的方法實現 switch(model) { case 'The Speedster': bicycle = new Speedster(); break; case 'The Lowrider': bicycle = new Lowrider(); break; case 'The Comfort Cruiser': default: bicycle = new Cruiser(); } // 對自行車進行組裝 bicycle.assemble(); // 對自行車進行清洗 bicycle.wash(); // 將自行車交付於客戶 return bicycle; } }; // 那麼,要出售某種型號的自行車,只要調用sellBicycle方法便可 var californiaCruisers = new BicycleShop(); // 購買你所須要的自行車 var youNeedBicycle = californiaCruisers.sellBicycle('The Speedster');
在狀況發生以前,這倒也挺管用。但要是你想在供貨目錄中加入一款新車型又會怎麼樣呢?你得爲此修改BicycleShop的代碼,哪怕這個類的實際功能實際上並無發生改變——依舊是建立一個自行車的新實例,組裝它、清洗它,而後把它交給客戶。更好的辦法是把sellBicycle方法中「建立新實例」這部分工做轉交給一個簡單工廠對象。模塊化
var BicycleFactory = { createBicycle: function(model) { var bicycle; switch(model) { case 'The Speedster': bicycle = new Speedster(); break; case 'The Lowrider': bicycle = new Lowrider(); break; case 'The Comfort Cruiser': default: bicycle = new Cruiser(); } return bicycle; } }; // 這裏的BicycleFactory是一個單體,用來把createBicycle方法封裝在一個命名空間中。 // 這個方法返回一個實現了Bicycle接口的對象,而後你能夠照常對其進行組裝和清洗。 var BicycleShop = function() {}; BicycleShop.prototype = { // 賣自行車 sellBicycle: function(model) { var bicycle; // 應用工廠模式建立所須要的自行車 bicycle = BicycleFactory.createBicycle(model); // 對自行車進行組裝 bicycle.assemble(); // 對自行車進行清洗 bicycle.wash(); // 將自行車交付於客戶 return bicycle; } };
這樣,這個BicycleFactory對象能夠供各類類來建立新的自行車實例。有關可供車型的全部信息都集中在一個地方管理,因此添加更多車型很容易。從而售車商店BicycleShop與添加具體的自行車車型達到解耦。函數
以上就是簡單工廠的一個很好的例子。這種模式把成員對象的建立工做轉交給一個外部對象。這個外部對象能夠像本例中同樣是一個簡單的命名空間,也能夠是一個類的實例。若是負責建立吃力的方法的邏輯不會發生變化,那麼通常說來用單體或靜態類方法建立這些成員實例是合乎情理的。但若是你要提供幾種不一樣品牌的自行車,那麼更恰當的作法是把這個建立方法實如今一個類中,並從該類派生出一些子類。ui
2、工廠模式(工廠方法)this
真正的工廠模式與簡單工廠模式的區別在於,它不是另外使用一個類或對象來建立自行車,而是使用一個子類。按照正式定義,工廠是一個將其成員對象的實例化推遲到子類中進行的類。spa
// BicycleShop class (abstract) // 該類不能被實例化,只能用來派生子類。 // 設計一個經銷特定自行車生產廠家產品的子類須要擴展BicycleShop,從新定義其中的createBicycle方法 var BicycleShop = function() {}; BicycleShop.prototype = { // 賣自行車 sellBicycle: function(model) { var bicycle = this.createBicycle(model); bicycle.assemble(); bicycle.wash(); return bicycle; }, // 建立自行車 createBicycle: function(model) { throw new Error('Unsupported operation on an abstract class.'); } };
如今咱們來定義兩個商店,一個商店從Acme公司進貨,另外一個則從GeneralProducts公司進貨。也就是繼承BicycleShop的具體子類
// 從Acme公司進貨的商店 var AcmeBicycleShop = function() {}; AcmeBicycleShop.prototype = new BicycleShop(); AcmeBicycleShop.prototype.createBicycle = function(model) { var bicycle; switch(model) { case 'The Speedster': bicycle = new AcmeSpeedster(); break; case 'The Lowrider': bicycle = new AcmeLowrider(); break; case 'The Comfort Cruiser': default: bicycle = new AcmeCruiser(); } return bicycle; };
// 從GeneralProducts公司進貨的商店 var GeneralProductsBicycleShop = function() {}; GeneralProductsBicycleShop.prototype = new BicycleShop(); GeneralProductsBicycleShop.prototype.createBicycle = function(model) { var bicycle; switch(model) { case 'The Speedster': bicycle = new GeneralProductsSpeedster(); break; case 'The Lowrider': bicycle = new GeneralProductsLowrider(); break; case 'The Comfort Cruiser': default: bicycle = new GeneralProductsCruiser(); } return bicycle; };
選擇同一個自行車款式,不一樣品牌的車子——
var AcmeShop = new AcmeBicycleShop(); AcmeShop.sellBicycle('The Lowrider'); var GeneralProductsShop = new GeneralProductsBicycleShop(); GeneralProductsShop.sellBicycle('The Lowrider');
由於兩個生產廠家生產的自行車款式徹底相同,因此顧客買車時能夠不用關心車到底是哪家生產的。要是他們只想要Acme生產的自行車,他們能夠去Acme專賣店買。
增長對其餘生產廠家的支持很簡單,只要再建立一個BicycleShop的子類並重定義其createBicycle工廠方法便可。咱們也能夠對各個子類進行修改,以支持相關廠家其餘型號的產品。這是工廠模式最重要的特色。對Bicycle進行通常性操做的代碼能夠所有寫在父類BicycleShop中,而對具體的Bicycle對象進行實例化的工做則被留到子類中。
3、使用原則
1. 動態實現
建立一些用不一樣方式實現同一接口的對象,那麼可使用一個工廠方法或簡單工廠對象來簡化選擇實現的過程。你一般要與一系列實現了同一個接口、能夠被同等對待的類打交道。這是JavaScript中使用工廠模式的最多見的緣由。
2. 節省設置開銷
若是對象須要進行復雜而且彼此相關的設置,那麼使用工廠模式能夠減小每種對象所需的代碼量。 若是這種設置只須要爲特定類型的全部實例執行一次便可,這種做用尤爲突出。把這種設置代碼放到類的構造函數中並非一個高效的作法,這是由於即使設置工做已經完成,每次建立新實例的時候這些代碼仍是會執行,並且這樣作會把設置代碼分散到不一樣的類中。工廠方法很是適合於這種場合。它能夠在實例化全部須要的對象以前先一次性地進行設置。不管有多少不一樣的類會被實例化,種方法均可以讓設置代碼集中在一個地方。 若是所用的類要求加載外部庫的話,這尤爲有用。工廠方法能夠對這些庫進行檢查並動態加載那些未找到的庫。這些設置代碼只存在於一個地方,所以之後改起來也方便得多。
4、優點及劣勢
優點——
工廠模式的主要好處在於消除對象間的耦合。經過使用工廠方法而不是new關鍵字及具體類,你能夠把全部實例化代碼集中在一個位置。這能夠大大簡化更換所用的類或在運行期間動態選擇所用的類的工做。在派生子類時它提供了更大的靈活性。使用工廠模式,你能夠先建立一個抽象的父類,而後在子類中建立工廠方法,從而把成員對象的實例化推遲到更專門化的子類中進行。
全部這些好處都與面向對象設計的這兩條原則有關:弱化對象間的耦合;防止代碼的重複。在一個方法中進行類的實例化,能夠消除重複性的代碼。這是在用一個對接口的調用取代一個具體的實現。這些都有助於建立模塊化的代碼。
劣勢——
可能有人禁不住想把工廠方法當作萬金油去用,把普通的構造函數仍在一塊兒。這並不值得提倡。若是根本不可能另外換用一個類,或者不須要在運行期間在一系列可互換的類中進行選擇,那就不該該使用工廠方法。大多數類最好使用new關鍵字和構造函數公開地進行實例化。這可讓代碼更簡單已讀。你能夠一眼就看到調用的是什麼構造函數,而沒必要去查看某個工廠方法以便知道實例化的是什麼類。工廠方法在其適用場合很是有用,但切勿濫用。若是拿不定主意,那就不要用,由於之後在重構代碼時還有機會使用工廠模式。
源自:JavaScript設計模式(人民郵電出版社)——第七章,工廠模式