工廠模式定義:「Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.」(在基類中定義建立對象的一個接口,讓子類決定實例化哪一個類。工廠方法讓一個類的實例化延遲到子類中進行。)
抽象工廠這塊知識,對入行以來一直寫純 JavaScript 的同窗可能不太友好——由於抽象工廠在很長一段時間裏,都被認爲是 Java/C++ 這類語言的專利。javascript
Java/C++ 的特性是什麼?它們是強類型的靜態語言。用這些語言建立對象時,咱們須要時刻關注類型之間的解耦,以便該對象往後能夠表現出多態性。但 JavaScript,做爲一種弱類型的語言,它具備自然的多態性,好像壓根不須要考慮類型耦合問題。而目前的 JavaScript 語法裏,也確實不支持抽象類的直接實現,咱們只能憑藉模擬去還原抽象類。所以有一種言論認爲,對於前端來講,抽象工廠就是雞肋。前端
但如今,不要看到「抽象」兩個字轉身就走,雞肋不雞肋理解清楚了纔有發言權。java
在實際的業務中,咱們每每面對的複雜度並不是數個類、一個工廠能夠解決,而是須要動用多個工廠。segmentfault
咱們繼續看上個小節舉出的例子,簡單工廠函數最後長這樣:ide
function Factory(name, age, career) { var work; switch(career) { case 'employees': work = ["辦存款", "放貸款", "收貸款"]; case 'president': work = ["喝茶", "看報紙", "..."]; case 'chairman': work = ["喝水", "放貸簽字", "開會"]; case xxx: // 工種對應職責 ... } return new User(name, age, career, work); }
乍看之下是沒什麼問題,但仔細看上去首個問題就是咱們把行長和普通職工放在了一塊兒。行長和職工在職能上的差異仍是很大的:首先,權限不一樣;其次,對一個系統的操做也不一樣;再者,......函數
那怎麼辦呢?要在工廠方法里加入相關的邏輯判斷嗎?單從功能實現上是沒有問題的。但這麼作實則在挖坑,由於銀行的工種多着呢,不止有行長、普通職工、還有主任、支行長、分行長等,他們的權限、職能有很大的不一樣。若是按照這個思路,每出現一個工種就在 Factory 增長相應的邏輯,那首先會形成這個工廠方法異常龐大,大到最終你不敢增長/修改任何地方,生怕致使 Factory 出現 bug 影響現有系統邏輯,也使得其難以維護。其次,每增長一個工種的邏輯就須要測試人員對 Factory 方法整個邏輯進行迴歸,給測試人員帶來額外的工做量。而這一切的源頭就是沒有遵照軟件設計的開放封閉原則。咱們再複習一下開放封閉原則的內容:對拓展開放,對修改封閉。說得更準確點,軟件實體(類、模塊、函數)能夠擴展,可是不可修改。測試
咱們先不急於理解具體的概念,先來看下面的例子:設計
有一天咱們來到銀行,給大堂經理說我要辦一張借記卡、一張信用卡。不管什麼卡都有相同的屬性,好比均可以存錢(雖然信用卡存錢沒有利息)、轉帳(僞裝信用卡能夠轉帳)。對於銀行也同樣,農行能夠辦卡,工行也能夠辦卡,那麼這兩家銀行也具有一樣的功能。code
又有一天咱們想組裝一臺主機,咱們知道主機由內存條、硬盤、CPU、電源、顯卡等組成,而內存條、硬盤等部件也有不少不一樣品牌廠家生產,一時之間咱們定很差想組裝一臺什麼配置的主機。不要緊,咱們能夠先約定一個抽象主機類,讓它具備各類硬件屬性,接着在對各硬件進行抽象,這樣咱們就擁有了抽象工廠類和抽象產品類。對象
上面的場景是屬於抽象工廠的例子,卡類屬於抽象產品類,制定產品卡類所具有的屬性,而銀行和以前的工廠模式同樣,負責生產具體產品實例,經過大堂經理就能夠拿到卡。其實,銀行也能夠被抽象爲銀行類,繼承這個類的銀行實例都有辦卡的功能,這樣就完成了抽象類對實例的約束。
// 抽象工廠類 class BankFactory { constructor() { if (new.target === BankFactory) { throw new Error("抽象工廠類不能直接實例化!"); } } // 抽象方法-辦卡 createBankCard() { throw new Error("抽象工廠類不容許直接調用,請重寫實現!"); } // 抽象方法-存錢 saveMoney() { throw new Error("抽象工廠類不容許直接調用,請重寫實現!"); } } // 具體銀行類 class Icbc extends BankFactory { createBankCard(type) { switch (type) { case "debit": return new DebitCard(); case "credit": return new CreditCard(); default: throw new Error("暫時沒有這個產品!"); } } } // 抽象產品類 class Card { // 抽象產品方法 buy() { throw new Error("抽象產品方法不容許直接調用,請從新實現!"); } transfer() { throw new Error("抽象產品方法不容許直接調用,請從新實現!"); } } // 具體借記卡類 class DebitCard extends Card { buy() { console.log("您可使用工行借記卡進行消費了!"); } transfer() { console.log("您可使用工行借記卡進行轉帳了!"); } } // 具體信用卡類 class CreditCard extends Card { buy() { console.log("您可使用工行信用卡進行消費了!"); } transfer() { console.log("您可使用工行信用卡進行轉帳了!"); } } const myBank = new Icbc(); const myCard = myBank.createBankCard("debit"); myCard.buy();
這種方式對原有的系統不會形成任何潛在影響,所謂的「對擴展開放,對修改封閉」就比較圓滿的實現了。
你們如今回頭對比一下抽象工廠和簡單工廠的思路,思考一下:它們之間有哪些異同?
它們的共同點,在於都嘗試去分離一個系統中變與不變的部分。它們的不一樣在於場景的複雜度。
在簡單工廠的使用場景裏,處理的對象是類,而且是一些相對簡單的類——它們的共性容易抽離,同時由於邏輯自己比較簡單,於是不期許代碼很高的可擴展性。
抽象工廠本質上處理的也是類,可是是相對更加繁雜的類,這些類中不只能劃分出門派,還能劃分出等級,同時存在着很高的擴展可能性——這使得咱們必須對共性做更特別的處理、使用抽象類去下降擴展的成本,同時須要對類的性質做劃分,因而有了這樣的四個關鍵角色: