工廠模式包含三種,簡單工廠模式,工廠方法模式,抽象工廠模式。這三種都是解決了一個問題,那就是對象的創建問題。他們的職責就是將對象的創建和對象的使用分離開來。
當我們創建對象的時候,總是會new一個對象,有錯么?技術上,new沒有錯,畢竟是C#的基礎部分,真正的犯人是我們的老朋友“改變”。以及他是如何影響new使用的。
針對接口編程,可以隔離掉以后系統可能發生的一大堆改變,為什么呢?如果代碼是針對接口而寫,那么通過多態,他可以與任何新類實現該接口,但是,當代碼使用大量的具體類時,一旦加入新的具體類,就必須改變代碼。違反了開閉原則了。
簡單工廠
我們先來看一下要訂購一個披薩的方法:
public static Pizza OrderPizza(string pizzaType) { Pizza pizza; switch (pizzaType) { case "cheese": pizza= new CheesePizza(); break; case "ApplePie": pizza= new ApplePiePizza(); break; default: pizza=new SomeOtherPizza(); break; } pizza.Prepare(); pizza.Beak(); pizza.Cut(); pizza.Box(); return pizza; }
如果讓我來實現一個訂購披薩的系統,我肯定會這么寫代碼,因為這種的代碼是能下意識的就寫出來,幾乎不用耗費我們的腦筋。但是,仔細看一下這段代碼:這個方法的目的是要訂購披薩,但是,訂購披薩的代碼中還要有關於生產pizza的知識,這個方法必須要知道所有的pizza,更加糟糕的是,如果將來要擴充pizza的種類或者刪減銷量不好的pizza,那么就會來修改這個訂購披薩的代碼了。看起來很糟糕,應該修改一下。我們第一步要做的一定是要將生產pizza的代碼和訂購披薩的代碼分開來,因為你不應該讓訂購pizza的代碼知道如何生產披薩的邏輯,應該將生產pizza的邏輯單獨交給一個專門的類型來負責。這樣,就可以應對將來的變化——如果要新增或者刪除pizza,我們可以直接修改pizza的生產者而不影響到訂購pizza這個邏輯。
而專門生產pizza的這個對象,我們可以叫他為工廠。看一下實現的代碼:
public class SimpleFactory { public Pizza CreatePizza(string pizzaType) { switch (pizzaType) { case "cheese": return new CheesePizza(); case "ApplePie": return new ApplePiePizza(); default: return new SomeOtherPizza(); } } }
public class PizzaStore { private SimpleFactory _factory; public PizzaStore(SimpleFactory factory) { this._factory = factory; } public Pizza OrderPizza(string pizzaType) { var pizza = _factory.CreatePizza(pizzaType); pizza.Prepare(); pizza.Beak(); pizza.Cut(); pizza.Box(); return pizza; } }
把創建對象的邏輯放到一個單獨的類中的原因是它可以被多個客戶端使用。並且把對象的創建和使用分離開來。但是這樣的設計也存在很多缺陷,首先,SimpleFactory是一個具體的類,那么我們就必須針對實現編程,使得系統失去了這方面的彈性,二是如果增加新的產品我們還得修改simplefactory中的代碼,這違反了開閉原則。上面這個模式也叫做簡單工廠。他沒有在GOF的模式中出現,它更像是一個編程的習慣。
可以看到PizzaStore依賴一個SImpleFactory,SimpleFactory依賴一個抽象的pizza。正常情況下,要做解耦,就要做控制反轉,控制反轉的核心思想是上層結構和底層結構都不依賴實現,而是依賴抽象。
工廠方法模式
現在來做一些改良,直接上代碼:
public abstract class FactoryPizzaStore { public Pizza OrderPizza(string pizzaType) { Pizza pizza = CreatePizza(pizzaType); pizza .Prepare(); pizza.Beak(); pizza.Cut(); pizza.Box(); return pizza; } public abstract Pizza CreatePizza(string pizzaType); }
現在,PizzaStrore(FactoryPizzaStore)是抽象的。CreatePizza方法從簡單工廠移植回來了。在PizzaStroe里面,CreatePizza方法現在是抽象的。
現在已經有一個抽象的PizzaStore類,其他類可以從這個類進行擴展,並重寫自己的CreatePizza方法。
解釋一下,在OrderPizza方法中,我們做了很多事情,包括調用CreatePizza方法產生一個想要的pizza,然后對pizza進行一系列的操作,關鍵在於,Pizza被定義為一個抽象類,也就是說在OrderPizza方法中我們並不關心這個Pizza在運行時到底是一個什么類型,換句話說,這就是解耦。解除了PizzaStore和Pizza之間的耦合。
那么現在,原本是通過一個對象負責所有具體類的實例化,現在通過對PizzaStore做的一些改變,變成由一群子類來對具體類的實例化:FactoryPizzaStore的子類負責實現CreatePizza;
更進一步的:工廠方法用來處理對象的創建,並把這種創建對象的實現延遲到子類中去實現,這樣,客戶代碼中關於基類的代碼就和子類對象創建代碼解耦了。
由於Pizza還是一個抽象類,當我們創建一個FactoryPizzaStore的子類的時候,也應該實現一個具體的繼承自Pizza的子類,如果只創建一個FactoryPizzaStore的子類,而沒有相對應的Pizza的子類,那么我們的披薩店賣什么呢?
public abstract class Pizza { public string Name { get; set; } public string Dough { get; set; } public string Sauce { get; set; } public List<string> Toopings { get; set; } public void Prepare() { Console.WriteLine($"Preparing {Name}"); Console.WriteLine("Tossing dough"); Console.WriteLine("Adding sauce"); Console.WriteLine("Adding Toppings.."); foreach (string item in Toopings) { Console.WriteLine($" {item}"); } } public void Cut() { Console.WriteLine("Cutting the pizza.."); } public void Bake() { Console.WriteLine("Baking the pizza"); } public void Box() { Console.WriteLine("Boxing the pizza"); } }
在創建一些Pizza的子類后,就可以開始測試了:
static void Main(string[] args) { FactoryPizzaStore store=new NyPizzaStore(); var pizza = store.OrderPizza("Cheese"); Console.WriteLine(pizza.Name); Console.ReadKey(); }
認識工廠方法模式:所有的工廠方法模式都是用來創建對象的。工廠方法模式通過讓子類決定該創建的對象是什么,來達到將對象創建過程進行封裝的目的。工廠方法模式有如下幾個角色:
1、創建者:就是上面定義的FactoryPizzaStore。它是一個抽象類,它定義了創建對象的方法,這個方法是一個抽象方法,具體的創建對象的過程由其子類來實現。這個抽象類通常會依賴一個抽象的產品(Product,工廠方法模式中的另一個角色)類。而這些抽象產品由子類制造,創建者並不關心制造的是哪種產品。
2、產品類:就是上面定義的Pizza,它也是一個抽象類,具體的產品由其子類來實現。
我們已經看到,將一個OrderPizza方法和一個工廠方法聯合起來(就是FactoryPizzaStore里面的那兩個方法),就形成了一個框架。除此之外,工廠方法吧生產知識封裝到各個創建者子類,這也可以形成一個框架。
下面給出工廠方法的定義:定義了一個創建對象的接口,但由子類決定要具體創建哪一個。工廠方法讓這種創建對象的過程推遲到子類。
簡單工廠和工廠方法之間的差異:簡單工廠和工廠方法模式看起來很相似,尤其是簡單工廠和工廠方法中的具體工廠。簡單工廠吧全部的事情,在一個地方都做完了。然而工廠方法創建的是一個框架,讓子類決定如何實現。比方說,在工廠方法中,OrderPizza方法創建了一般的框架,以便創建披薩,OrderPizza方法依賴工廠方法創建具體的類,並制造出實際的披薩。可通過繼承工廠,決定制造的實際產品到底是什么。簡單工廠不具備這方面的彈性。
封裝變化
將創建對象的代碼封裝起來,實例化具體的工廠類,達到了封裝實例化目的。將創建對象的代碼封裝到一個地方,也可以達到復用的目的,並且更方便以后的維護。這也意味着客戶在實例化對象時,只會依賴接口,不會依賴具體的實現。幫助我們更好的達到針對接口編程而不是實現,讓代碼更有彈性,以應對未來的擴展。