是時候弄一套開分店的標準了—抽象工廠

anly_jun發表於2019-03-03

前情提要

上集講到, 小光(利用原型模式)Copy了光谷店的模式, 成功開張了創業街分店.

現在兩家分店都運營得不錯, 小光閒暇之時, 又陷入了思考(思考是個好習慣). 琢磨著, 這次開分店, 我是完全clone了光谷店的那一套, 然後修改了一些個屬性(例如分店名字什麼的). 但是, 以後小光熱乾麵的發展, 還會開出很多分店呢, 而且沒家分店可能不僅僅是名字不一樣, 還有一些諸如飲料杯上的地址什麼的也會不一樣, clone起來是很方便, 但是萬一哪次忘了改屬性了, 就麻煩了.

想到著, 小光心想, 是時候制定一套開分店的標準流程/方式了.

所有示例原始碼已經上傳到Github, 戳這裡

分店都有什麼

根據現有的經驗, 小光很快總結出來了目前按照他的方式開一家熱乾麵店基本包括的一系列東西:

  1. 店鋪
  2. 收銀臺
  3. 杯具餐具
  4. 熱乾麵流程
  5. 飲料機套路
  6. 活動策略

為便於區分講解, 我們以前三個為例.

建立開分店的標準方式

制定每項東西標準

本著良好的面向介面程式設計思維, 小光照例給這些東西制定了一些標準(介面).

門面:

public interface Store {

    // 地址
    String getAddress();

    // 店鋪名
    String getName();
}複製程式碼

收銀臺:

public interface Checkstand {

    // 銀行賬戶
    String getAccount();
}複製程式碼
public interface Tableware {

    // 標籤
    String getLabel();
}複製程式碼

統一配置生產

接下來, 小光要做的就是想著統一下每個分店開店的標準, 既要避免開分店時別遺漏了什麼, 也能根據不同分店的配置開出不一樣的分店. (開閉原則)

開分店的標準如下:

public interface CompanyFactory {

    Store createStore();

    Checkstand createCheckstand();

    Tableware createTableware();
}複製程式碼

按照這個方式開分店

好的, 讓我們來試下按照這個標準套路來開出SBI創業街分店.

先實現創業街分店的Store, Checkstand和Tableware:

public class SbiStore implements Store {

    @Override
    public String getAddress() {
        return "關山創業街";
    }

    @Override
    public String getName() {
        return "SBI店";
    }
}

public class SbiCheckStand implements Checkstand {

    @Override
    public String getAccount() {
        return "招商銀行:620123131231233";
    }
}

public class SbiTableware implements Tableware {
    @Override
    public String getLabel() {
        return "SBI";
    }
}複製程式碼

然後, 實現一個SBI的專屬工廠來生成這些東西:

public class SbiCompanyFactory implements CompanyFactory {
    @Override
    public Store createStore() {
        return new SbiStore();
    }

    @Override
    public Checkstand createCheckstand() {
        return new SbiCheckStand();
    }

    @Override
    public Tableware createTableware() {
        return new SbiTableware();
    }
}複製程式碼

讓我們利用SBI工廠生成的東西組建一個分店吧:

public class XiaoGuang {

    public static void main(String[] args) {

        CompanyFactory factory = new SbiCompanyFactory();

        // 來根據factory生產出來的東西構建一個分店:
        Company sbiCompany = new Company(factory.createStore(), factory.createCheckstand(), factory.createTableware());

        System.out.println(sbiCompany);
    }
}複製程式碼

結果:

分店{地址:關山創業街, 名字:SBI店, 收銀賬戶:招商銀行:620123131231233, 杯具餐具標籤:SBI}複製程式碼

不負眾望啊, 建立出來SBI店了.

以後我們就可以根據這種方式開分店了

例如, 如果想開一家花山軟體新城店, 按照上面開創業街店的方式:

  1. 實現花山店的店鋪 HuashanStore.
  2. 實現花山店的收銀臺 HuashanCheckStand.
  3. 實現花山店的餐具 HuashanTableware.
  4. 實現生產花山店的這些東西的工廠 HuashanCompanyFactory.

程式碼就不一一貼了, 完整程式碼戳這裡.

來看下怎麼開出花山分店:

// 建立Huashan工廠
factory = new HuashanCompanyFactory();

// 根據factory生產出來的東西構建花山店:
Company huashanCompany = new Company(factory.createStore(), factory.createCheckstand(), factory.createTableware());

System.out.println(huashanCompany);複製程式碼

結果:

分店{地址:花山軟體新城, 名字:花山店, 收銀賬戶:建設銀行:62610000000000, 杯具餐具標籤:HS}複製程式碼

好的, 花山店建立完畢.

故事之後

在這次建立建立分店的標準中, 小光一直想做就是: 確定分店的開設方式標準, 避免修改原有的分店產品, 方便擴充套件開出新的分店.

這個實際上就是我們屢次提到的開閉原則的思想, 即對修改關閉(不修改原有的), 對擴充套件開放(方便擴充套件出新的產品/例項).

慣例, 我們來梳理下類之間的關係:

是時候弄一套開分店的標準了—抽象工廠

沒錯, 這個就是我們今天的主角 — 抽象工廠模式.

抽象工廠
提供一個建立一系列相關或互相依賴的物件的介面, 而無需指定它們的具體實現.

在本例中這個介面就是CompanyFactory, 它建立了一系列相關的物件(Store, Checkstand, Tableware), 並且這些物件並非具體實現(也是介面). 具體的物件例項是由實現了CompanyFactory介面的HuashanCompanyFactory, SbiCompanyFactory來建立的.

擴充套件閱讀一

到目前為止, 算上這個, 我們提到了三種工廠:

  1. 小光熱乾麵提供飲料了 — 簡單工廠
  2. 光氏飲品升級了 — 工廠方法
  3. 是時候弄一套開分店的標準了 — 抽象工廠

那麼這三者有什麼區別呢?

我們首先來對比下三者的類圖:

是時候弄一套開分店的標準了—抽象工廠

簡單工廠
實際上我們可以理解為是一種程式設計習慣, 將類似物件的初始化放下一個地方, 便於管理.
它提供了一個工廠(表妹), 來根據不同的指令(drinkType)來生產不同的飲料產品(橙汁, 可樂, 酸梅湯).
相對簡單, 適用於要建立類似(實現同一介面的)的產品, 且產品種類不多, 擴充套件可能性不大的情況. 當需要增加一中飲料時, 我們需要修改工廠(表妹)的實現, 增加drinkType的對應實現.

工廠方法
顧名思義, 有一個工廠, 工廠(飲料機)裡有那麼一個方法(定義了一個建立物件的介面makeDrink), 可以生產產品(Drink). 由實現了這個工廠方法的類來決定具體生產出什麼產品(可以是可樂, 橙汁, 奶茶等).

相比於簡單工廠, 工廠方法有良好的擴充套件性, 當我們需要增加一種飲料時, 不需要去修改工廠, 只需擴充套件一個新的工廠, 實現其工廠方法, 提供新的飲料即可.

這實際上就是典型的, 通過繼承/實現, 來達成了對修改關閉, 對擴充套件開放的效果.

另外, 從簡單工廠到工廠方法, 我們也可以理解為是一次Switch Statements的重構.

對於”Switch Statements的重構”, 有興趣的同學可以參看<<重構–改善既有程式碼的設計>>一書的3.10節. 那是一本好書, 2010年的時候華為的一位技術經理推薦給我的, 感謝他.

另外, 並不是我們以後遇到Switch就要想著改造, 遇到簡單工廠就想著用工廠方法…還需根據實際情況取用合適的.

抽象工廠
同樣, 從名字中, 我們大致能瞭解, 抽象工廠描述的一個抽象的工廠, 其可以生產一系列的相關的或是互相依賴的產品.

抽象工廠和工廠方法有很多類似之處, 都是建立產品, 都是通過繼承/實現, 來達成了對修改關閉, 對擴充套件開放的效果.

然而, 抽象工廠相較於工廠方法, 它的重點, 是它解決的是一個產品族(相關的, 或是互相依賴的產品們)的建立問題, 而非僅僅是一類產品.

以本故事來說, 工廠方法是用來建立一類產品, 通過他建立出來的都是飲料. 而抽象工廠是用來建立一系列產品, 包括店鋪, 收銀臺, 餐具等, 這些產品是相關的, 都是一個分店所需要的.

打個比方, 如果我有一個輪胎工廠, 我生產的東西都是輪胎, 只是規格不同, 我就可以使用工廠方法; 如果我是一個汽車工廠, 我生產汽車, 它需要輪胎, 車架, 發動機… 那麼我就應用使用抽象工廠.

擴充套件閱讀二

前文, 原型模式我們講到, 原型工廠, 會經常跟工廠一起用. 那麼它一般會與哪種工廠一起用呢, 沒錯, 就是我們今天說的抽象工廠.

為什麼呢?
我們回過頭來看下, 我們抽象工廠建立的過程, 是不是建立了很多物件啊. 是的, 抽象工廠如果生產的產品系列很多的話, 我們會發現我們會建立很多很多的物件(產品), 而這些物件有的時候是可能一樣的, 有可能是很類似的.

例如, 我們的分店工廠建立了收銀臺(Checkstand), 這個不同的分店的收銀臺可能都是一樣的, 稍微有點不同可能是僅僅是賬戶不一樣, 我們沒有必要去重新建立一個.

回想下, 原型模式的使用 — 使用clone的方式來快速建立一個新的(與原型物件例項一致的)物件例項. 這種方法可以避免我們屢次複雜的建立.

我們當前的Checkstand建立可能相對簡單, 想象下, 如果Checkstand足夠複雜的時候, clone將會是很有用的.

具體的實現, 大家可以私下去改造下本例程式碼, 融合下原型模式, 體驗下.


OK, 建立出開分店的標準了, 小光想著, 以後開分店都不用我親力親為的…哈哈.

相關文章