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

2020-06-30

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

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