重溫《深入淺出設計模式》工廠模式 (Book Review of Head First Design Pattern, Factory Pattern)
2020-06-30
工廠是物件實體化的工廠,在實體化衍生類別的時候,如果演算邏輯相當複雜或者有改變的需求,可以將其抽出,依需求的情境分別以最基本的簡單工廠包裝生產邏輯,進階的將生產的方法以衍生類別包裝實作,以工廠方法時做,或者更進階地將相同主題類別加以包裝,並以蘊含工廠方法的方式多型地完成生產的工作。
簡單工廠模式 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)