【设计模式】工厂方法模式 Factory Method Pattern
在簡單工廠模式中產品的創建統一在工廠類的靜態工廠方法中創建,體現了面形對象的封裝性,客戶程序不需要知道產品產生的細節,也體現了面向對象的單一職責原則(SRP),這樣在產品很少的情況下使用起來還是很方便, 但是如果產品很多,并且不斷的有新產品加入,那么就會導致靜態工廠方法變得極不穩定,每次加入一個新產品就要修改靜態工廠方法,這違背了面向對象設計原則的開閉原則(OCP)。那么在應對這種不斷增加的新產品,簡單工模式有些力不從心了,那么什么模式可以完美應對呢?這就是這篇文章要談到的工廠方法模式。在工廠方法模式中,我們不再提供一個統一的工廠類來創建所有的產品對象,而是針對不同的產品提供不同的工廠類,系統提供一個與產品等級結構對應的工廠等級結構。
一、工廠方法模式定義
工廠方法模式(Factory Method Pattern):定義一個用于創建對象的接口,讓子類決定將哪一個類實例化。工廠方法模式讓一個類的實例化延遲到其子類。工廠方法模式又簡稱為工廠模式(Factory Pattern),又可稱作虛擬構造器模式(Virtual Constructor Pattern)或多態工廠模式(Polymorphic Factory Pattern)。
二、工廠方法模式結構圖
工廠方法模式結構圖
1.IProduct (抽象產品角色):
它是定義產品的接口,是工廠方法模式所創建對象的父類,也就是產品對象的公共父類,這個角色一般可以有抽象類或者接口來擔當。
2.ConcreteProduct(具體產品):
它實現了抽象產品接口,某種類型的具體產品由專門的具體工廠創建,具體工廠和具體產品之間一一對應。
3.Factory(抽象工廠):
在抽象工廠類中,聲明了工廠方法(Factory Method),用于返回一個產品。抽象工廠是工廠方法模式的核心,所有創建具體對象的具體工廠類都必須實現該接口。
4. ConcreteFactory(具體工廠):
它是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,并可由客戶端調用,返回一個具體產品類的實例。
與簡單工廠模式相比,工廠方法模式最重要的區別是引入了抽象工廠角色,抽象工廠可以是接口,也可以是抽象類或者具體類
三、工廠方法模式代碼實現:
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();} }客戶端調用:
static void Main() {//使用ConcreteFactoryA 創建 ProductAIFactory factoryA = new ConcreteFactoryA();IProduct productA = factoryA.Create();productA.DoSomething();//使用ConcreteFactoryB 創建 ProductBIFactory factoryB = new ConcreteFactoryB();IProduct productB = factoryB.Create();productB.DoSomething();Console.ReadKey(); }輸出結果:
?
四、重構音頻播放器實例得到工廠方法模式
在簡單工廠模式中我們舉了一個音頻播放器的例子,開發人員從開始直接創建對象中逐步隨著需求的改變最終得到了簡單工廠模式, 完美的解決了播放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 playIFactory wmaFactory = new WmaFactory();IAudio wamAudio = wmaFactory.Create();wamAudio.Play("take me to your hert");//Wav playIFactory wavFactory = new WavFactory();IAudio wavAudio = wavFactory.Create();wavAudio.Play("take me to your hert");//Mp3 playIFactory mp3Factory = new Mp3Factory();IAudio mp3Audio = mp3Factory.Create();mp3Audio.Play("take me to your hert");//Acc playIFactory 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文件也掃描進去,不然當前程序集中實現的工廠無法找到。
轉載于:https://www.cnblogs.com/vaiyanzi/p/9359474.html
總結
以上是生活随笔為你收集整理的【设计模式】工厂方法模式 Factory Method Pattern的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重庆社保局密码重置
- 下一篇: asp.net 获得域名,端口,虚拟目录