JavaScript設計模式-抽象工廠模式

工廠模式定義:「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();

這種方式對原有的系統不會形成任何潛在影響,所謂的「對擴展開放,對修改封閉」就比較圓滿的實現了。

總結

你們如今回頭對比一下抽象工廠和簡單工廠的思路,思考一下:它們之間有哪些異同?

它們的共同點,在於都嘗試去分離一個系統中變與不變的部分。它們的不一樣在於場景的複雜度。
在簡單工廠的使用場景裏,處理的對象是類,而且是一些相對簡單的類——它們的共性容易抽離,同時由於邏輯自己比較簡單,於是不期許代碼很高的可擴展性。
抽象工廠本質上處理的也是類,可是是相對更加繁雜的類,這些類中不只能劃分出門派,還能劃分出等級,同時存在着很高的擴展可能性——這使得咱們必須對共性做更特別的處理、使用抽象類去下降擴展的成本,同時須要對類的性質做劃分,因而有了這樣的四個關鍵角色:

  • 抽象工廠(抽象類,它不能被用於生成具體實例): 用於聲明最終目標產品的共性。在一個系統裏抽象工廠能夠有多個,每個抽象工廠對應的這一類產品,被稱爲「產品族」。
  • 具體工廠(用於生成產品族裏的一個具體的產品): 繼承自抽象工廠、實現了抽象工廠裏聲明的方法,用於建立具體的產品的類。
  • 抽象產品(抽象類,它不能被用於生成具體實例): 上面咱們看到,具體工廠裏實現的接口,會依賴一些類,這些類對應到各類各樣的具體的細粒度產品(好比借記卡、信用卡),這些具體產品類的共性各自抽離,便對應到了各自的抽象產品類。
  • 具體產品(用於生成產品族裏的一個具體的產品所依賴的更細粒度的產品): 文中具體的一張借記卡或信用卡或者組裝的主機裏的一個內存條、一塊硬盤等。
相關文章
相關標籤/搜索