在簡單工廠模式中產品的建立統一在工廠類的靜態工廠方法中建立,體現了面形對象的封裝性,客戶程序不須要知道產品產生的細節,也體現了面向對象的單一職責原則(SRP),這樣在產品不多的狀況下使用起來仍是很方便, 可是若是產品不少,而且不斷的有新產品加入,那麼就會致使靜態工廠方法變得極不穩定,每次加入一個新產品就要修改靜態工廠方法,這違背了面向對象設計原則的開閉原則(OCP)。那麼在應對這種不斷增長的新產品,簡單工模式有些力不從心了,那麼什麼模式能夠完美應對呢?這就是這篇文章要談到的工廠方法模式。在工廠方法模式中,咱們再也不提供一個統一的工廠類來建立全部的產品對象,而是針對不一樣的產品提供不一樣的工廠類,系統提供一個與產品等級結構對應的工廠等級結構。html
工廠方法模式(Factory Method Pattern):定義一個用於建立對象的接口,讓子類決定將哪個類實例化。工廠方法模式讓一個類的實例化延遲到其子類。工廠方法模式又簡稱爲工廠模式(Factory Pattern),又可稱做虛擬構造器模式(Virtual Constructor Pattern)或多態工廠模式(Polymorphic Factory Pattern)。數據庫
工廠方法模式結構圖app
它是定義產品的接口,是工廠方法模式所建立對象的父類,也就是產品對象的公共父類,這個角色通常能夠有抽象類或者接口來擔當。學習
它實現了抽象產品接口,某種類型的具體產品由專門的具體工廠建立,具體工廠和具體產品之間一一對應。測試
在抽象工廠類中,聲明瞭工廠方法(Factory Method),用於返回一個產品。抽象工廠是工廠方法模式的核心,全部建立具體對象的具體工廠類都必須實現該接口。spa
它是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,並可由客戶端調用,返回一個具體產品類的實例。設計
與簡單工廠模式相比,工廠方法模式最重要的區別是引入了抽象工廠角色,抽象工廠能夠是接口,也能夠是抽象類或者具體類3d
public interface IProduct { void DoSomething(); } public interface IFactory { IProduct Create(); } public class ConcreteProductA : IProduct { public void DoSomething() { Console.WriteLine("I'm Product A"); } } public class ConcreteProductB : IProduct { public void DoSomething() { Console.WriteLine("I'm Product B"); } } public class ConcreteFactoryA : IFactory { public IProduct Create() { return new ConcreteProductA(); } } public class ConcreteFactoryB : IFactory { public IProduct Create() { return new ConcreteProductB(); } }
客戶端調用:code
static void Main() { //使用ConcreteFactoryA 建立 ProductA IFactory factoryA = new ConcreteFactoryA(); IProduct productA = factoryA.Create(); productA.DoSomething(); //使用ConcreteFactoryB 建立 ProductB IFactory factoryB = new ConcreteFactoryB(); IProduct productB = factoryB.Create(); productB.DoSomething(); Console.ReadKey(); }
輸出結果:orm
在簡單工廠模式中咱們舉了一個音頻播放器的例子,開發人員從開始直接建立對象中逐步隨着需求的改變最終獲得了簡單工廠模式, 完美的解決了播放MP3,WAV,WMA格式的音頻文件。最終代碼看起來是這樣:
public interface IAudio { void Play(string name); } public class Wma : IAudio { public void Play(string name) { Console.WriteLine("Start playing wma file..."); Console.WriteLine($"The song name is: [{name}.wma]"); Console.WriteLine(".........."); } } public class Wav : IAudio { public void Play(string name) { Console.WriteLine("Start playing wav file..."); Console.WriteLine($"The song name is: [{name}.wav]"); Console.WriteLine(".........."); } } public class Mp3 : IAudio { public void Play(string name) { Console.WriteLine("Start playing mp3..."); Console.WriteLine($"The song name is: [{name}.mp3]"); Console.WriteLine(".........."); } } public class AudioFactory { public static IAudio Create(string songType) { IAudio audio; switch (songType.ToUpper()) { case "A": audio = new Wav(); break; case "M": audio = new Wma(); break; case "P": audio = new Mp3(); break; default: throw new ArgumentException("Invalid argument", nameof(songType)); } return audio; } } [Description("1.2. Simple Factory")] public class App { static void Main() { Console.WriteLine("Please input a or m or p"); var input = Console.ReadKey(); if (input != null) { IAudio audio = AudioFactory.Create(input.Key.ToString()); audio.Play("take me to your hert"); } Console.ReadKey(); } }
輸出結果:
看起來很不錯,完美的解決了播放WMA,WAV和MP3 格式的音頻文件,可是音樂文件的格式不斷在發展增多,所以播放器也要經過不斷的升級來支持不斷涌現的新格式的音頻文件。 甲方已經提出來了支持MPEG, MPEG-4 等等格式的文件,每次開發人員都要新增一個具體的音頻格式的類,而且在工廠的靜態方法中建立一個case條件來支持新的格式文件。日積月累,隨着時間的推移,swich case 的邏輯變得異常的龐大和複雜,很難維護了,這不,最近甲方提出來要支持acc格式文件的播放,此次升級終因而產生了一次事故, 開發人員從甲方哪裏拿到要支持acc音頻格式的文件需求,輕車熟路建立了個acc的產品文件類,可是忘記在swich case 中加這個case就將代碼編譯打包提交給甲方。因爲甲方和開發人員過去每次配合的都很好,這一次他就絕對的信任了開發人員,因而沒有測試新的版本就直接發佈到市場上投入了商業使用。結果可想而知根本就播放不了acc格式的音頻文件。 甲方知道此過後很生氣,勒令開發人員立馬修復bug從新發布版本,可是市場是瞬息萬變的,就由於這麼一個失誤的發佈,市場上的竟品軟件就很快蠶食了甲方播放器的市場。開發人員不敢怠慢,加班加點,找出bug並修復從新打包交付甲方,甲方趕忙將新版本通過充分測試後投入到市場。
隨後開發人員準備找出容易出現這種錯誤緣由,將這種犯錯的機會扼殺在搖籃。除了自身的粗心以外,他還想從代碼上找到一些緣由。因而他Review了一下本身的代碼, 他發現工廠類中的靜態工廠方法的邏輯太複雜了,翻滾了好幾個屏幕,看了一個多小時才把這裏面的代碼理順看清楚了, 看完後發發現靜態工廠方法的職責隨着產品的增多在不斷的增多, 工廠方法的負擔過重了, 他決定重構這個地方的代碼,他指望將建立具體產品的職責單提取到獨的一個類中來完成,一個類負責一個具體產品的建立,因而他提出了個這個建立具體產品的抽象接口IFactory, 而後讓具體建立類都繼承自這個接口, 經過重構代碼,如今音頻播放器的代碼變成了這樣:
public interface IAudio { void Play(string name); } public interface IFactory { IAudio Create(); } public class Wma : IAudio { public void Play(string name) { Console.WriteLine("Start playing wma file..."); Console.WriteLine($"The song name is: [{name}.wma]"); Console.WriteLine(".........."); } } public class Wav : IAudio { public void Play(string name) { Console.WriteLine("Start playing wav file..."); Console.WriteLine($"The song name is: [{name}.wav]"); Console.WriteLine(".........."); } } public class Mp3 : IAudio { public void Play(string name) { Console.WriteLine("Start playing mp3..."); Console.WriteLine($"The song name is: [{name}.mp3]"); Console.WriteLine(".........."); } } public class Acc : IAudio { public void Play(string name) { Console.WriteLine("Start playing Acc..."); Console.WriteLine($"The song name is: [{name}.acc]"); Console.WriteLine(".........."); } } public class WmaFactory : IFactory { public IAudio Create() { return new Wma(); } } public class WavFactory : IFactory { public IAudio Create() { return new Wav(); } } public class Mp3Factory : IFactory { public IAudio Create() { return new Mp3(); } } public class AccFactory : IFactory { public IAudio Create() { return new Acc(); } } [Description("2.1. Factory Mothed payer")] public class App { static void Main() { //Wma play IFactory wmaFactory = new WmaFactory(); IAudio wamAudio = wmaFactory.Create(); wamAudio.Play("take me to your hert"); //Wav play IFactory wavFactory = new WavFactory(); IAudio wavAudio = wavFactory.Create(); wavAudio.Play("take me to your hert"); //Mp3 play IFactory mp3Factory = new Mp3Factory(); IAudio mp3Audio = mp3Factory.Create(); mp3Audio.Play("take me to your hert"); //Acc play IFactory accFactory = new AccFactory(); IAudio accAudio = accFactory.Create(); accAudio.Play("take me to your hert"); Console.ReadKey(); } }
運行軟件輸出結果:
代碼重構完成,結構符合預期,在回過頭來Review 一下代碼,這不就是Factory Method Pattern嗎? 這樣開發人員就將這種場景下的代碼構造的比較合理了。甲方再增長新的音頻文件格式時,就很容易應對了,只須要建立一個具體產品而且再建立一個具體的工廠類來建立這個產品就能夠了。這樣軟件更符合面向對象設計原則的SRP 和OCP原則了。
下來問題來了, 若是甲方提出須要這個播放器軟件支持視頻播放,開發人員應該怎麼辦能? 那麼 隨着學習其餘模式就能找到更合理的答案。
以上面的音樂播放器爲例, 若是在特定的場景下只須要播放MP3 格式的音樂,而在另外一些特定的場景下只須要播放ACC 格式的音樂, 怎麼辦呢?
這裏使用配置+反射動態建立 工廠的方式來實現這個需求, 首先來看怎麼實現僅支持MP3的狀況:
1. 在配置文件App.config中增長一個配置:
<appSettings> <add key="MethodFactory" value="DesignPattern.MehodFactory.AudioInstance.Mp3Factory"/> </appSettings>
2.在調用處讀取上面的配置文件並使用反射獲得具體工廠,而後調用Play 方法,代碼以下:
static void AudioMethodFactoryExecuteBySetting() { DesignPattern.MehodFactory.AudioInstance.IFactory factory=null; var setting = ConfigurationSettings.AppSettings["MethodFactory"]; string dir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); string[] assemblies = Directory.GetFiles(dir, "*.exe"); List<Type> types = new List<Type>(); foreach (var s in assemblies) { var ass=Assembly.LoadFile(s); foreach(Type t in ass.GetExportedTypes()){ if(t.IsClass && typeof( DesignPattern.MehodFactory.AudioInstance.IFactory).IsAssignableFrom(t)){ if(t.FullName==setting){ factory=Activator.CreateInstance(t) as DesignPattern.MehodFactory.AudioInstance.IFactory; } } } } if (factory == null) return; IAudio audio = factory.Create(); audio.Play("take me to your hert"); }
輸出結果:
若是需求改成僅支持ACC格式的音頻文件,就很容易實現了,僅僅須要修改配置文件中配置的具體工廠類的字符串就能夠了,其它任何地方都不須要改變,而且不須要編譯應用程序就能夠正常工做了。
這裏咱們找到應用程序生成的目錄:F:\source\DesignPattern\DesignPattern.MehodFactory\bin\Debug,在這裏咱們看到有下列文件:
咱們只須要用寫字板打開配置文件 DesignPattern.MehodFactory.exe.config 修改配置爲須要支持的ACCFactory就能夠了
而後雙擊文件DesignPattern.MehodFactory.exe運行,結果以下:
咱們看到僅僅只改變了一下配置就輕鬆實現了應用功能的熱替換,不須要作任何的編譯和代碼上的修改。
假如這個應用程序是從其它的軟件開發商那裏買來的,如今你的老闆然你開發一個新的功能,須要在某些場景下僅支持AAR格式的音頻文件,該怎麼辦呢?。
1.新建一個控制檯應用程序
假設如今沒有源代碼,可是還要實現支持AAR的音頻格式文件的播放, 首先須要從新建一個C# 的工程文件,建立一個控制檯應用程序,這裏我命名爲DesignPattern.Extension, 而後建立一個Aar class 並繼承IAudio接口,再建立一個AarFactory class並集成IFactory接口, 代碼以下:
public class AarFactory : DesignPattern.MehodFactory.AudioInstance.IFactory { public IAudio Create() { return new Aar(); } } public class Aar : IAudio { public void Play(string name) { Console.WriteLine("Start playing Aar..."); Console.WriteLine(string.Format("The song name is: [{0}.aar]", name)); Console.WriteLine(".........."); } }
寫完代碼後編譯DesignPattern.Extension 應用程序而後找到生成的DesignPattern.Extension.exe 文件, 而後拷貝到F:\source\DesignPattern\DesignPattern.MehodFactory\bin\Debug, 以下:
2. 修改配置文件以下:
3. 雙擊DesignPattern.MehodFactory.exe 運行, 看到下面的結果輸出:
咱們看到輸出的正是Arr文件的邏輯。
這樣就輕鬆實現了無源碼的擴展。
注意:這裏我使用的是控制檯應用程序,其擴展名是.exe, 因此在反射的時候我掃碼的是當前工做目錄下的全部exe後綴的文件,若是是類庫工程,就要掃描當前工做目錄下的dll文件。而且還要將exe文件也掃描進去,否則當前程序集中實現的工廠沒法找到。