重溫《深入淺出設計模式》工廠模式 (Book Review of Head First Design Pattern, Factory Pattern)


  1. 簡單工廠模式 Simple Factory
    1. Before Pattern
    2. After Pattern
  2. 工廠方法模式 Factory Method
    1. Before Pattern
    2. After Pattern
  3. 抽象工廠模式 Abstract Factory
    1. After Pattern
  4. 小結
  5. 參考資料

工廠是物件實體化的工廠,在實體化衍生類別的時候,如果演算邏輯相當複雜或者有改變的需求,可以將其抽出,依需求的情境分別以最基本的簡單工廠包裝生產邏輯,進階的將生產的方法以衍生類別包裝實作,以工廠方法時做,或者更進階地將相同主題類別加以包裝,並以蘊含工廠方法的方式多型地完成生產的工作。

logo

簡單工廠模式 Simple Factory

Before Pattern

生產披薩的邏輯寫在單一類別,同時此類別依賴 Pizza 的衍生類別,如果有異動 (例如增加新的 Pizza 種類) if else 就必須調整。

public class PizzaStore {
  Pizza pizza = null;

  if (type == "cheese")
  {
    pizza = new CheesePizza();
  }
  else if (type == "pepperoni")
  {
    pizza = new PepperoniPizza();
  }
  else if (type == "clam")
  {
    pizza = new ClamPizza();
  }
  else if (type == "veggie")
  {
    pizza = new VeggiePizza();
  }

  pizza.Prepare();
  pizza.Bake();
  pizza.Cut();
  pizza.Box();
}

After Pattern

將會變動的生產條件抽出成為 SimplePizzaFactory ,而 PizzaStore 傳入種類引數項 SimplePizzaFactory 取得 Pizza ,並透過多型的方式實作 Pizza。如果有新種類的 Pizza 加入,僅需要調整 SimplePizzaFactory 而可以保持 PizzaStore 封閉。

簡單方法工廠被視為一種開發常用的習慣而非 Pattern,在簡單的情境中可以讓保持 Open Closed Principle,並且也不需要為維護及擴充額外費心。

public class SimplePizzaFactory {
  public Pizza createPizza(string type) {
    Pizza pizza = null;

    if (type == "cheese")
    {
      pizza = new CheesePizza();
    }
    else if (type == "pepperoni")
    {
      pizza = new PepperoniPizza();
    }
    else if (type == "clam")
    {
      pizza = new ClamPizza();
    }
    else if (type == "veggie")
    {
      pizza = new VeggiePizza();
    }
    return pizza;
  }
}

public class PizzaStore {
  SimplePizzaFactory factory;

  public PizzaStore(SimplePizzaFactory factory)
  {
    this.factory = factory;
  }

  public Pizza orderPizza(string type)
  {
    Pizza pizza;

    pizza = factory.createPizza(type);

    pizza.Prepare();
    pizza.Bake();
    pizza.Cut();
    pizza.Box();

    return pizza;
  }
}

工廠方法模式 Factory Method

Define an interface for creating an object, but let the subclasses decide which class to instantiate. The Factory method lets a class defer instantiation it uses to subclasses

如果有不同生產工廠的需求,但又要求不同的工廠有一致的介面時,可以採用工廠方法模式。此模式將生產工廠抽象化,並且可以實作為不同的工廠。

Before Pattern

在套用模式之前,大量的邏輯判斷必須配合需求異動,並且充滿耦合。

public abstract class Pizza
{
    public void Prepare(){}
    public void Bake(){}
    public void Cut(){}
    public void Box(){}
}

After Pattern

加入工廠方法模式後,將工廠抽象為 PizzaStore,並實作 ChicagoPizzaStore 及 NYPizzaStore 兩個工廠。


/PizzaStore.cs
<-ChicagoPizzaStore.cs
<-NYPizzaStore.cs

public abstract class PizzaStore
{
    public abstract Pizza CreatePizza(string item);

    public Pizza OrderPizza(String type)
    {
        Pizza pizza = CreatePizza(type);
        pizza.Prepare();
        pizza.Bake();
        pizza.Cut();
        pizza.Box();
        return pizza;
    }
}

/PizzaStore <- ChicagoPizzaStore.cs

public class ChicagoPizzaStore : PizzaStore
{
    private Pizza CreatePizza(string item)
    {
        if (item == "cheese")
        {
            return new ChicagoStyleCheesePizza();
        }
        else if (item == "veggie")
        {
            return new ChicagoStyleVeggiePizza();
        }
        else if (item == "clam")
        {
            return new ChicagoStyleClamPizza();
        }
        else if (item == "pepperoni")
        {
            return new ChicagoStylePepperoniPizza();
        }
        else return null;
    }
}

/Pizza.cs
<-ChicagoStyleCheesePizza.cs
<-ChicagoStyleClamPizza.cs
<-ChicagoStylePepperoniPizza.cs
<-ChicagoStyleVeggiePizza.cs
<-NYStyleCheesePizza.cs
<-NYStyleClamPizza.cs
<-NYStylePepperoniPizza.cs
<-NYStyleVeggiePizza.cs

public abstract class Pizza
{
    private string name;
    private string dough;
    private string sauce;
    List<string> toppings = new List<String>();

    public virtual void Prepare()
    {
    }

    public virtual void Bake()
    {
    }

    public virtual void Cut()
    {
    }

    public virtual void Box()
    {
    }
}

/PizzaTestDrive.cs

public class FactoryMethodTestDrive
{
  public static void Main(String[] args)
  {
    PizzaStore nyStore = new NYPizzaStore();
    PizzaStore chicagoStore = new ChicagoPizzaStore();

    Pizza pizza = nyStore.orderPizza("cheese");
    pizza = nyStore.orderPizza("pepperoni");
  }
}

抽象工廠模式 Abstract Factory

The Abstract Factory Design Pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes

使用 UML 可以更清楚的看清抽象工廠的全貌,主要就是由一個抽象工廠 (Ingredient Factory) 擁有一系列相關主題的類別 (Ingredients),並藉由實作抽象工廠 (NY, Chicago Ingredient Factory) 的類別來決定主題類別 (Calm, Dough) 的使用 (FreshCalm, FrozenCalm)。

另外 Client 的對象並不如案例中的限定是 PizzaStore,在比較小的應用情境下其實也可以是 Pizza 。

After Pattern

PizzaIngredientFactory.cs
<- ChicagoPizzaIngredientFactory.cs
<- NYPizzaIngredientFactory.cs

public interface IPizzaIngredientFactory
{
    Dough CreateDough();
    Sauce CreateSauce();
    Cheese CreateCheese();
    List<Veggies> CreateVeggies();
    Pepperoni CreatePepperoni();
    Clams CreateClam();
}

public class ChicagoPizzaIngredientFactory : IPizzaIngredientFactory
{
    public Dough CreateDough()
    {
        return new ThickCrustDough();
    }

    public Sauce CreateSauce()
    {
        return new PlumTomatoSauce();
    }

    public Cheese CreateCheese()
    {
        return new MozzarellaCheese();
    }

    public List<Veggies> CreateVeggies()
    {
        return new List<Veggies> { new BlackOlives(), new Spinach(), new Eggplant() };
    }

    public Pepperoni CreatePepperoni()
    {
        return new SlicedPepperoni();
    }

    public Clams CreateClam()
    {
        return new FrozenClams();
    }
}

public class NYPizzaIngredientFactory : IPizzaIngredientFactory
{
    public Cheese CreateCheese()
    {
        return new ReggianoCheese();
    }

    public Clams CreateClam()
    {
        return new FreshClams();
    }

    public Dough CreateDough()
    {
        return new ThinCrustDough();
    }

    public Pepperoni CreatePepperoni()
    {
        return new SlicedPepperoni();
    }

    public Sauce CreateSauce()
    {
        return new MarinaraSauce();
    }

    public List<Veggies> CreateVeggies()
    {
        return new List<Veggies> { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
    }
}

PizzaStore.cs
<- NYPizzaStore.cs
<- ChicagoPizzaStore.cs

public abstract class PizzaStore
{
    protected abstract Pizza CreatePizza(String item);

    public Pizza OrderPizza(String type)
    {
        Pizza pizza = CreatePizza(type);
        Console.WriteLine(("Making a " + pizza.GetName()));
        pizza.Prepare();
        pizza.Bake();
        pizza.Cut();
        pizza.Box();
        return pizza;
    }
}

public class NYPizzaStore : PizzaStore
{
    protected override Pizza CreatePizza(string item)
    {
        Pizza pizza = null;
        IPizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        if (item == "cheese")
        {
            pizza = new CheesePizza(ingredientFactory);
            pizza.SetName("New York Style Cheese Pizza");
        }
        else if (item == "veggie")
        {
            pizza = new VeggiePizza(ingredientFactory);
            pizza.SetName("New York Style Veggie Pizza");
        }

        return pizza;
    }
}

public class ChicagoPizzaStore : PizzaStore
{
    Pizza pizza = null;
    IPizzaIngredientFactory ingredientFactory = new ChicagoPizzaIngredientFactory();
    protected override Pizza CreatePizza(string item)
    {
        if (item == "cheese")
        {
            pizza = new CheesePizza(ingredientFactory);
            pizza.SetName("Chicago Style Cheese Pizza");
        }
        else if (item == "veggie")
        {
            pizza = new VeggiePizza(ingredientFactory);
            pizza.SetName("Chicago Style Veggie Pizza");
        }

        return pizza;
    }
}

Pizza.cs
<- CheesePizza.cs
<- PepperoniPizza.cs
<- ClamPizza.cs
<- VeggiePizza.cs

public abstract class Pizza
{
    internal string _name;
    internal Dough _dough;
    internal Sauce _sauce;
    internal List<Veggies> _veggies;
    internal Cheese _cheese;
    internal Pepperoni _pepperoni;
    internal Clams _clam;

    internal string GetName()
    {
        return _name;
    }

    internal abstract void Prepare();

    internal void Bake()
    {
        Console.WriteLine("Bake for 25 minutes at 350");
    }

    internal void Cut()
    {
        Console.WriteLine("Cutting the pizza into diagonal slices");
    }

    internal void Box()
    {
        Console.WriteLine("Place pizza in official PizzaStore box");
    }

    internal void SetName(string name)
    {
        _name = name;
    }
}

internal class CheesePizza : Pizza
{
    private IPizzaIngredientFactory ingredientFactory;

    public CheesePizza(IPizzaIngredientFactory ingredientFactory)
    {
        this.ingredientFactory = ingredientFactory;
    }

    internal override void Prepare()
    {
        Console.WriteLine($"Preparing {_name}");
        _dough = ingredientFactory.CreateDough();
        _sauce = ingredientFactory.CreateSauce();
        _cheese = ingredientFactory.CreateCheese();
    }
}

internal class VeggiePizza : Pizza
{
    private IPizzaIngredientFactory ingredientFactory;

    public VeggiePizza(IPizzaIngredientFactory ingredientFactory)
    {
        this.ingredientFactory = ingredientFactory;
    }

    internal override void Prepare()
    {
        Console.WriteLine($"Preparing {_name}");
        _dough = ingredientFactory.CreateDough();
        _sauce = ingredientFactory.CreateSauce();
        _cheese = ingredientFactory.CreateCheese();
        _veggies = ingredientFactory.CreateVeggies();
    }
}

以下皆為同主題類別,為 Ingredient Factory 擁有

ICheese.cs
<- MozzarellaCheese.cs
<- ReggianoCheese.cs
<- ParmesanCheese.cs

public interface ICheese
{
    string Tostring();
}

internal class MozzarellaCheese : ICheese
{
    public string Tostring()
    {
        return "Shredded Mozzarella";
    }
}

internal class ReggianoCheese : ICheese
{
    public string Tostring()
    {
        return "Reggiano Cheese";
    }
}

IDough.cs
<- ThickCrustDough.cs
<- ThinCrustDough.cs

IClams.cs
<- FreshClams.cs
<- FrozenClams.cs

ISauce.cs
<- PlumTomatoSauce.cs
<- MarinaraSauce.cs

IPepperoni.cs
<- SlicedPepperoni.cs

IVeggies.cs
<- BlackOlives.cs
<- Eggplant.cs
<- Garlic.cs
<- Mushroom.cs
<- Onion.cs
<- RedPepper.cs
<- Spinach.cs


PizzaTestDrive.cs

public static void Main(string[] args)
{
    PizzaStore nyStore = new NYPizzaStore();
    PizzaStore chicagoStore = new ChicagoPizzaStore();

    Pizza pizza = nyStore.OrderPizza("cheese");
    Console.WriteLine(pizza + "\n\n===================================\n");

    pizza = chicagoStore.OrderPizza("veggie");
    Console.WriteLine(pizza + "\n\n===================================\n");
}

小結

✔️將變動的部分封裝起來

  • 簡單工廠將創造的邏輯封裝至工廠中

✔️多用合成,少用繼承

  • 抽象工廠使用合成的方式封裝共同主題的類別家族

✔️針對介面撰寫,而非針對實踐方式撰寫。

✔️讓需要互動的物件之間的關係鬆綁

✔️類別應該對需求保持開放;應該對既有的程式碼修改保持關閉

  • 生產的演算邏輯是開放的,客戶的使用介面則是封閉的不受修改影響

✔️依賴抽象類別,不要依賴具象類別 (DIP)

參考資料

GitHub - Head-First-Design-Patterns