簡單工廠模式,工廠方法模式,抽象工廠模式,spring的狂想
菜鳥D在專案中遇見一個比較糾結的高耦合,所以就想辦法來解耦。情況是這樣的:系統通過使用者選擇treeview控制元件的節點判斷呼叫不同的處理,這些處理中某些東西又是類似的。同事的建議是採用簡單工廠,耦合就耦合吧,反正treeview節點基本是不會變化的。(能偷懶就偷懶吧)
菜鳥D有些偏執,想找些方法來解耦。於是就學習了這個幾種方法,雖然不一定用的上,多學一點總是好的。
首先說簡單工廠,例子是一個已經二到死的計算器。
簡單工廠由三種角色組成:工廠類角色(creator),抽象產品類角色(product,圖中為Iproduct介面),具體產品類角色(concreteproduct,圖中為Product_A…)。
程式碼如下:
public class Calculate { public double DValue1 { get; set; } public double DValue2 { get; set; } public virtual double GetResult() { return 0; } } public class Add : Calculate { public override double GetResult() {return DValue1 + DValue2; } } public class Sub : Calculate { public override double GetResult() { return DValue1 - DValue2; } } public class Mul : Calculate { public override double GetResult() { returnDValue1 * DValue2; } } public class Div : Calculate { public override double GetResult() { if (DValue2.Equals(0)) { if (DValue1.Equals(0)) { return 1; } return 0; } return DValue1 / DValue2; } }
#region 客戶端 Console.WriteLine("請輸入第一個數"); string v1 = Console.ReadLine(); Console.WriteLine("請輸入運算子"); string oper = Console.ReadLine(); Console.WriteLine("請輸入第二個數"); string v2 = Console.ReadLine(); Calculate c = GetCalculateByOper(oper); c.DValue1 = Convert.ToDouble(v1); c.DValue2 = Convert.ToDouble(v2); Console.WriteLine("結果:" + c.GetResult()); #endregion #region 工廠方法 private static Calculate GetCalculateByOper(string oper) { Calculate c = new Calculate(); switch (oper) { case "+": c = new Add(); break; case "-": c = new Sub(); break; case "*": c = new Mul(); break; case "/": c = new Div(); break; } return c; } #endregion
在這個例子中沒有使用抽象類,介面,而是用了虛方法(純粹是不想在接口裡定義屬性,自己喜歡用什麼就用什麼,畢竟這不是簡單工廠的核心)。簡單工廠的核心是充當工廠類角色的工廠方法(起碼在此例中是這樣),邏輯判斷中使具體產品類和工廠直接耦合了(好像類圖的關係已經決定了這種耦合,但是將具體產品和客戶端解耦)。如果要新增一個開方運算,首先新增開方的運算類,然後開啟工廠方法進行修改。(呵呵,違反了開閉原則)
接下來再說一下工廠方法模式,類圖如下:
先分析一下類圖,工廠模式有了四個角色:工廠介面,工廠實現,產品介面,產品實現(其實說角色不貼切,還不如說是種類)。工廠實現和產品實現有了依賴關係,通過呼叫工廠實現來獲取產品實現。也就是一種產品實現對應一種工廠實現。
還是那個二的要死的計算器的例子,程式碼如下:
public interface IFactory { ICalculatable GetCalculatable(); } public interface ICalculatable { double GetReslut(double d1, double d2); } public class Add : ICalculatable { public double GetReslut(double d1, double d2) { return d1 + d2; } } public class AddFactory : IFactory { public ICalculatable GetCalculatable() { return new Add(); } } public class Sub : ICalculatable { public double GetReslut(double d1, double d2) { return d1 - d2; } } public class SubFactory : IFactory { public ICalculatable GetCalculatable() { return new Sub(); } } public class Mul : ICalculatable { public double GetReslut(double d1, double d2) { return d1 * d2; } } public class MulFactory : IFactory { public ICalculatable GetCalculatable() { return new Mul(); } } public class Div : ICalculatable { public double GetReslut(double d1, double d2) { if (d2.Equals(0)) { if (d1.Equals(0)) { return 1; } return 0; } return d1 / d2; } } public class DivFactory : IFactory { public ICalculatable GetCalculatable() { return new Div(); } }
#region 邏輯判斷 public class Factory { public static IFactory GetCalculatableByOper(string oper) { IFactory factory = null; switch (oper) { case "+": factory = new AddFactory(); break; case "-": factory = new SubFactory(); break; case "*": factory = new MulFactory(); break; case "/": factory = new DivFactory(); break; } return factory == null ? null : factory; } } #endregion #region 客戶端 Console.WriteLine("請輸入第一個數"); string v1 = Console.ReadLine(); Console.WriteLine("請輸入運算子"); string oper = Console.ReadLine(); Console.WriteLine("請輸入第二個數"); string v2 = Console.ReadLine(); ICalculatable calculatable = Factory.GetCalculatableByOper(oper).GetCalculatable(); Console.WriteLine("結果:" + calculatable.GetReslut(Convert.ToDouble(v1), Convert.ToDouble(v2))); #endregion
注意:此處的判斷邏輯已經不屬於工廠模式了(類圖中很明確),也可以脫離客戶端。雖說簡單工廠也可以這樣將判斷邏輯脫離客戶端,但是簡單工廠的邏輯判斷是在工廠類中的,也就依然是簡單工廠模式的一部分(此處邏輯有些亂,不懂的就認真研究類圖)。通常在工廠模式的客戶端需要用到哪種工廠再去建立哪種工廠,而不是如例子中的使用一個判斷邏輯來選擇,也就是工廠模式建立工廠是通過“外部”的邏輯來確定,而模式本身是不會做判斷的。(所以這個例子並不是一個合適的例子)
此時,計算器要再新增一個開方的方法,只需新增方法實現,添加工廠實現,不會對原有的程式碼做修改,符合了開閉原則(你當我瞎啊,你不修改判斷邏輯嗎?說了多少次了,判斷邏輯部分不屬於工廠模式。不過比簡單工廠確實多寫不少東西)。
工廠方法模式通常在以下兩種情況中使用 :
第一,需要使用某種產品,客戶端很清楚應該使用哪個具體工廠,此種情況不存在上述的判斷邏輯,只需要例項化具體工廠,然後生產具體產品。
第二,需要使用某種產品,但是不想也不需要知道是哪個工廠建立,此種情況存在一定的判斷邏輯,但是客戶端不需要知道這個邏輯,生產方(工廠模式)是通過外部的邏輯來生產產品,這時由外部來例項化具體工廠,生產具體產品交付給客戶端。
接著再來說說抽象工廠,在抽象工廠中最明顯的一個特點是產品不止一類了,所以前文提到的計算機的例子就不適合使用抽象工廠模式(竊以為如此,有反對意見的歡迎提出)。
在抽象工廠在提到一個“產品族”的概念,其實在抽象工廠中最為直觀的體現就是產品A介面,產品B介面甚至是產品C、D介面。類圖如下,從類圖中不難發現,抽象工廠模式的具體工廠角色可以生產多種產品(工廠方法模式的具體工廠只能生產一種產品,不信去看類圖)。就好像東方紅拖拉機廠在工廠方法模式下只能生產拖拉機,但是在抽象工廠模式下不僅能生產拖拉機,還能生產卡車(如果加一個坦克的生產線,還能生產坦克)。
此處就用比較熟悉的農場的例子,農場生產水果和蔬菜,水果分為北方水果和熱帶水果,蔬菜也一樣,所以農場也分為北方農場和熱帶農場。
程式碼如下:
public interface IFruit { string Show(); } public interface IVeggie { string Show(); } public class NFruit : IFruit { private string name; public string Name { get { return "北方" + name; } set { name = value; } } public string Show() { return Name; } } public class TFruit : IFruit { string name; public string Name { get { return "熱帶" + name; } set { name = value; } } public string Show() { return Name; } } public class NVeggie : IVeggie { string name; public string Name { get { return "北方" + name; } set { name = value; } } public string Show() { return Name; } } public class TVeggie : IVeggie { string name; public string Name { get { return "熱帶" + name; } set { name = value; } } public string Show() { return Name; } } public interface Factory { IFruit CreateFruit(string nm); IVeggie CreateVeggie(string nm); } public class NFactory : Factory { public IFruit CreateFruit(string nm) { return new NFruit() { Name = nm }; } public IVeggie CreateVeggie(string nm) { return new NVeggie() { Name = nm }; } } public class TFactory : Factory { public IFruit CreateFruit(string nm) { return new TFruit() { Name = nm }; } public IVeggie CreateVeggie(string nm) { return new TVeggie() { Name = nm }; } }
此例的邏輯判斷部分沒寫。從類圖上看,它是不屬於抽象工廠模式的,從簡化客戶端的角度,這個邏輯判斷是可以從客戶端剝離的,很顯然和工廠方法模式的邏輯判斷屬於同一地位(有種兩頭受氣的感覺)。
然後再簡單說spring的依賴注入。在spring中需要獲取一個產品(例項)如何獲取?容器來提供。邏輯判斷呢?可以放在客戶端,也可以從客戶端剝離,愛放哪放哪。容器只依賴於配置,邏輯判斷不會影響容器。所以容器很好的將生產過程與客戶端隔離,這就不存在耦合了。
接下來再說一下,一直不停的重複的判斷邏輯。在菜鳥D 的看法裡,上述幾個模式的耦合都是存在於判斷邏輯中的。在簡單工廠中,工廠和客戶端之間耦合較低,但是工廠和具體的產品類是直接耦合的。在工廠方法模式、抽象工廠模式、spring中,邏輯判斷只是一個輔助,邏輯判斷將生產過程和客戶端隔離,大大地降低了耦合程度。所以在衡量設計模式的耦合時,就需要衡量判斷邏輯在模式中作用和角色。有些耦合是很難避免的,為了避免這些耦合甚至可能會造成更多的耦合。至於開閉原則,編碼過程儘量去注意,否則為以後的開發帶來麻煩就不是我們想要的了。
一家之言,不值一哂,如有謬誤,歡迎指正。
菜鳥D希望這篇文章對您有所幫助。
擴充套件參考:
大話設計模式P72:
工廠方法實現時,客戶端需要決定例項化哪一個工廠來實現運算類(具體產品),選擇判斷的問題還是存在的,也就是說,工廠方法吧簡單工廠的內部邏輯判斷移到了客戶端程式碼來進行.想要新增功能,本來是要改工廠類的,而現在是修改客戶端.
三種工廠模式區別總結
spring.net的依賴注入
工廠方法模式
三種工廠模式