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

goodspeed發表於2017-10-28

問題:在上一篇 python設計模式:工廠方法模式我們嘗試使用工廠方法建立了披薩店,現在為了保證披薩加盟店也能有良好的聲譽,我們需要統一原材料,這個該如何做呢?

為了確保每家加盟店都是用高質量的原材料,我們打算建造一加原材料工廠,並將原材料運送到各個加盟店。每個加盟店會對原材料有不同的需求,這裡我們就可以用上上一篇介紹的工廠方法模式了。

  1. 首先,建造原料工廠
  2. 然後建造區域的原料工廠(繼承自原料工廠)
  3. 在區域的原料工廠中實現原料的建立方法。
  4. 將原料工廠組合起來,加入到 PizzaStore(上一篇中由工廠方法實現)程式碼中。

按照這個思路,我們先建立原料工廠

建立原料工廠

建立原料工廠的實現程式碼如下:

# 原料
class FreshClams:

    def __str__(self):
        return 'Fresh Clams'

class MarinaraSauce:

    def __str__(self):
        return "Marinara Sauce"

class ThickCrustDough:

    def __str__(self):
        return "Thick Crust Dough"

class ReggianoCheese:

    def __str__(self):
        return "Reggiano Cheese"

class SlicedPepperoni:

    def __str__(self):
        return "Sliced Pepperoni"

class Garlic:

    def __str__(self):
        return "Garlic"

class Onion:

    def __str__(self):
        return "Onion"

class RedPepper:

    def __str__(self):
        return "Red Pepper"

# 披薩店原料工廠
class PizzaIngredientFactory:

    '''
    定義原料工廠
    '''

    def create_dough(self):
        raise NotImplementedError()

    def create_sauce(self):
        raise NotImplementedError()

    def create_cheese(self):
        raise NotImplementedError()

    def create_pepperoni(self):
        raise NotImplementedError()

    def create_clam(self):
        raise NotImplementedError()

    def create_veggies(self):
        raise NotImplementedError()複製程式碼

在這個工廠中,每個原料都是一個方法,原料的實現需要在具體的原料工廠中實現。
這裡每個原料方法沒有做任何工作,只是丟擲了NotImplementedError 這樣做是為了強制子類重新實現相應的方法,如果不重新實現用到時就會丟擲 NotImplementedError。

當然也可以把 PizzaIngredientFactory 的 metaclass 設定成 abc.ABCMeta 這樣的話,這個類就是真正的抽象基類。

建立紐約原料工廠

class NYPizzaIngredientFactory(PizzaIngredientFactory):
    def create_dough(self):
        print("Tossing %s" % ThickCrustDough())
        return ThickCrustDough()

    def create_sauce(self):
        print("Adding %s..." % MarinaraSauce())
        return MarinaraSauce()

    def create_cheese(self):
        print("Adding %s..." % ReggianoCheese())
        return ReggianoCheese()

    def create_pepperoni(self):
        print("Adding %s..." % SlicedPepperoni())
        return SlicedPepperoni()

    def create_clam(self):
        print("Adding %s..." % FreshClams())
        return FreshClams()

    def create_veggies(self):
        # 蔬菜可能有多種,這裡使用列表
        veggies = [Garlic(), Onion(), RedPepper()]
        for veggie in veggies:
            print("  %s" % veggie)
        return veggies複製程式碼

對於原料家族的每一種原料,我們都提供了原料的紐約版本。

重做 Pizza 類

class Pizza:

    name = None
    dough = None
    sauce = None
    cheese = None
    veggies = []
    pepperoni = None
    clam = None

    def prepare(self):
        raise NotImplementedError()

    def bake(self):
        print("Bake for 25 minutes at 350")

    def cut(self):
        print("Cutting the pizza into diagonal slices")

    def box(self):
        print("Place pizza in official PizzaStore box")

    def __str__(self):
        return self.name複製程式碼

上述程式碼和工廠方法的程式碼相比,只是把 prepare() 方法抽象出來,需要相應的 具體的 pizza 類來實現 prepare()

實現 芝加哥芝士披薩

class NYStyleCheesePizza(Pizza):

    def prepare(self):
        dough = self.ingredient_factory.create_dough()
        sauce = self.ingredient_factory.create_sauce()
        cheese = self.ingredient_factory.create_cheese()
        clam = self.ingredient_factory.create_clam()
        veggies = self.ingredient_factory.create_veggies()複製程式碼

從上述程式碼可以發現,Pizza 的原料也是從原料工廠直接獲取,現在我們控制了原料。

現在,Pizza 類不需要關心原料,只需要負責製作 pizza 就好。Pizza 和原料被解耦。

重新實現 PizzaStore

class PizzaStore:

    # 需要宣告原料工廠
    ingredient_factory = None

    def create_pizza(self, pizza_type):
        # 每個需要子類實現的方法都會丟擲NotImplementedError
        # 我們也可以把 PizzaStore 的 metaclass 設定成 abc.ABCMeta
        # 這樣的話,這個類就是真正的抽象基類
        raise NotImplementedError()

    def order_pizza(self, pizza_type):  # 現在把 pizza 的型別傳入 order_pizza()

        pizza = self.create_pizza(pizza_type)

        #  一旦我們有了一個 pizza,需要做一些準備(擀麵皮、加佐料),然後烘烤、切片、裝盒
        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()
        return pizza

class NYStylePizzStore(PizzaStore):

    # 將需要用到的原料工廠賦值給變數 ingredient_factory
    ingredient_factory = NYPizzaIngredientFactory()

    def create_pizza(self, pizza_type):
        # 根據 pizza 型別,我們例項化正確的具體類,然後將其賦值給 pizza 例項變數
        if pizza_type == 'cheese':
            pizza = NYStyleCheesePizza('NY Style Sauce and Cheese Pizza',
                                       self.ingredient_factory)
        elif pizza_type == 'clam':
            pizza = NYStyleClamPizza('NY Style Clam Pizza',
                                     self.ingredient_factory)
        return pizza複製程式碼

通過上述程式碼可以看到我們做了以下工作:

  1. 引入了新型別的工廠(抽象工廠)來建立原料家族
  2. 通過抽象工廠提供的介面,我們建立了原料家族。
  3. 我們的原料程式碼從實際的 Pizza 工廠中成功解耦,可以應用到不同地方,響應的,我們可以方便的替換原料工廠來生產不同的 pizza。

來看下下單的程式碼

def main():
    nystore = NYStylePizzStore()
    pizza = nystore.order_pizza('cheese')
    print('*' * 10)
    print("goodspeed ordered a %s" % pizza)
    print('*' * 10)複製程式碼

和工廠方法的程式碼相比,沒有任何改變。

[原始碼參考python-design-patter-abstract-factory.py](https://gist.github.com/gusibi/5e0797f5458678322486f999ca87a180)

抽象工廠模式

抽象工廠模式提供一個介面,用於建立相關或依賴物件的家族,而不需要指定具體類。

也就是說,抽象工廠允許客戶使用抽象的介面來建立一組相關的產品,而不需要知道實際產出的具體產品是什麼,這樣依賴,客戶就從具體產品中被解耦。

概括來說就是,抽象工廠是邏輯上的一組工廠方法,每個工廠方法各司其職,負責生產不同種類的物件。

我們來看下 抽象工廠模式 的類圖:

抽象工廠模式類圖
抽象工廠模式類圖

抽象工廠在 django_factory 中應用比較多,有興趣的可以看下原始碼。

抽象工廠模式 和 工廠方法模式 的比較

抽象工廠模式 和 工廠方法模式 都是負責建立物件,但

  • 工廠方法模式使用的是繼承
  • 抽象工廠模式使用的是物件的組合

這也就意味著利用工廠方法建立物件需要擴充套件一個類,並覆蓋它的工廠方法(負責將客戶從具體類中解耦)。
抽象工廠提供一個用來建立產品家族的抽象型別,這個型別的子類定義了產品被產生的方法。要想使用這個工廠(NYPizzaIngredientFactory),必須先例項化它(ingredient_factory = NYPizzaIngredientFactory()),然後將它傳入一些針對抽象型別所寫的程式碼中(也做到了將客戶從具體產品中解耦),同時還把一群相關的產品集合起來。

工廠方法模式和抽象工廠模式如何選擇

開始的時候,可以選擇工廠方法模式,因為他很簡單(只需要繼承,並實現工廠方法即可)。如果後來發現應用需要用到多個工廠方法,那麼是時候使用抽象工廠模式了,它可以把相關的工廠方法組合起來。

抽象工廠模式優點和缺點

優點

  • 可以將客戶從具體產品中解耦
  • 抽象工廠可以讓物件建立更容易被追蹤
  • 同時將物件建立與使用解耦
  • 也可以優化記憶體佔用提升應用效能

缺點

因為抽象工廠是將一組相關的產品集合起來,如果需要擴充套件這組產品,就需要改變介面,而改變介面則意味著需要改變每個子類的介面

參考連結


最後,感謝女朋友支援。

歡迎關注(April_Louisa) 請我喝芬達
歡迎關注
歡迎關注
請我喝芬達
請我喝芬達

相關文章