在C#中使用依賴注入-工廠模式和工廠方法模式
- 2020 年 3 月 16 日
- 筆記
工廠模式和工廠方法模式是設計模式中較為常見的兩種模式,藉助於依賴注入可以更好的發揮模式的特性。本文將通過一個業務需求的變化過程來闡述如何更好的使用設計模式與依賴注入。
業務需求
在用戶登錄之後,需要向用戶發送一條簡訊,通知用戶。
程式碼演練
公共內容
namespace Use_Dependency_Injection_With_Factory_Pattern { /// <summary> /// 發送簡訊 /// </summary> public interface ISmsSender { /// <summary> /// 發送簡訊 /// </summary> /// <param name="phone">手機</param> /// <param name="message">簡訊 </param> void Send(string phone, string message); } /// <summary> /// 用戶業務 /// </summary> public interface IUserBll { /// <summary> /// 登錄 /// </summary> /// <param name="phone"></param> /// <param name="password"></param> bool Login(string phone, string password); } public interface IUserDal { bool Exists(string phone, string password); } public class UserDal : IUserDal { private const string StaticPassword = "newbe"; private const string StaticPhone = "yueluo"; public bool Exists(string phone, string password) { // 使用固定的帳號密碼驗證 return phone == StaticPhone && password == StaticPassword; } } }
公共內容中包含了業務需求中的主要介面ISmsSender和IUserBll,以及演示用的數據存儲層介面與實現。
版本1,使用控制台「發送簡訊」
using Autofac; using System; namespace Use_Dependency_Injection_With_Factory_Pattern { public static class Demo1 { public static void Run() { Console.WriteLine($"開始運行{nameof(Demo1)}"); var cb = new ContainerBuilder(); cb.RegisterType<UserDal>().As<IUserDal>(); cb.RegisterType<UserBll>().As<IUserBll>(); cb.RegisterType<ConsoleSmsSender>().As<ISmsSender>(); var container = cb.Build(); var userBll = container.Resolve<IUserBll>(); var login = userBll.Login("yueluo", "newbe"); Console.WriteLine(login); login = userBll.Login("newbe", "yueluo"); Console.WriteLine(login); Console.WriteLine($"結束運行{nameof(Demo1)}"); } public class UserBll : IUserBll { private readonly IUserDal _userDal; private readonly ISmsSender _smsSender; public UserBll( IUserDal userDal, ISmsSender smsSender) { _userDal = userDal; _smsSender = smsSender; } public bool Login(string phone, string password) { var re = _userDal.Exists(phone, password); if (re) { _smsSender.Send(phone, "您已成功登錄系統"); } return re; } } public class ConsoleSmsSender : ISmsSender { public void Send(string phone, string message) { Console.WriteLine($"已給{phone}發送消息:{message}"); } } } }
簡要分析。版本1使用構造函數注入實現了程式碼的解耦,使用Autofac作為容器管理,常規用法,沒有問題。
由於沒有正常的簡訊發送調用,所以使用ConsoleSmsSender在控制台中輸出消息進行模擬發送。
版本2,線上版本「真發簡訊」,開發版本「假髮簡訊」
using Autofac; using System; namespace Use_Dependency_Injection_With_Factory_Pattern { public static class Demo2 { public static void Run() { Console.WriteLine($"開始運行{nameof(Demo2)}"); var cb = new ContainerBuilder(); cb.RegisterType<UserDal>().As<IUserDal>(); cb.RegisterType<UserBll>().As<IUserBll>(); // 使用預編譯命令,使得 Release 和 Debug 版本註冊的對象不同,從而實現調用的簡訊API不同 #if DEBUG cb.RegisterType<ConsoleSmsSender>().As<ISmsSender>(); #else cb.RegisterType<HttpApiSmsSender>().As<ISmsSender>(); #endif var container = cb.Build(); var userBll = container.Resolve<IUserBll>(); var login = userBll.Login("yueluo", "newbe"); Console.WriteLine(login); login = userBll.Login("newbe", "yueluo"); Console.WriteLine(login); Console.WriteLine($"結束運行{nameof(Demo2)}"); } public class UserBll : IUserBll { private readonly IUserDal _userDal; private readonly ISmsSender _smsSender; public UserBll( IUserDal userDal, ISmsSender smsSender) { _userDal = userDal; _smsSender = smsSender; } public bool Login(string phone, string password) { var re = _userDal.Exists(phone, password); if (re) { _smsSender.Send(phone, "您已成功登錄系統"); } return re; } } /// <summary> /// 調試·簡訊API /// </summary> public class ConsoleSmsSender : ISmsSender { public void Send(string phone, string message) { Console.WriteLine($"已給{phone}發送消息:{message}"); } } /// <summary> /// 真·簡訊API /// </summary> public class HttpApiSmsSender : ISmsSender { public void Send(string phone, string message) { Console.WriteLine($"已調用API給{phone}發送消息:{message}"); } } } }
簡要分析。與版本1相比,增加了真實調用簡訊API的實現類,並且通過預編譯的方式,實現了Debug模式和Release模式下發布不同版本的效果。一定程度上已經完成了需求。
版本3,引入工廠模式,實現「簡訊API選擇」邏輯的分類
using Autofac; using System; namespace Use_Dependency_Injection_With_Factory_Pattern { public static class Demo3 { public static void Run() { Console.WriteLine($"開始運行{nameof(Demo3)}"); var cb = new ContainerBuilder(); cb.RegisterType<UserDal>().As<IUserDal>(); cb.RegisterType<UserBll>().As<IUserBll>(); cb.RegisterType<SmsSenderFactory>().As<ISmsSenderFactory>(); cb.RegisterType<ConfigProvider>().As<IConfigProvider>(); var container = cb.Build(); var userBll = container.Resolve<IUserBll>(); var login = userBll.Login("yueluo", "newbe"); Console.WriteLine(login); login = userBll.Login("newbe", "yueluo"); Console.WriteLine(login); Console.WriteLine($"結束運行{nameof(Demo3)}"); } public class UserBll : IUserBll { private readonly IUserDal _userDal; private readonly ISmsSenderFactory _smsSenderFactory; public UserBll( IUserDal userDal, ISmsSenderFactory smsSenderFactory) { _userDal = userDal; _smsSenderFactory = smsSenderFactory; } public bool Login(string phone, string password) { var re = _userDal.Exists(phone, password); if (re) { var smsSender = _smsSenderFactory.Create(); smsSender.Send(phone, "您已成功登錄系統"); } return re; } } /// <summary> /// 調試·簡訊API /// </summary> public class ConsoleSmsSender : ISmsSender { public void Send(string phone, string message) { Console.WriteLine($"已給{phone}發送消息:{message}"); } } /// <summary> /// 真·簡訊API /// </summary> public class HttpApiSmsSender : ISmsSender { public void Send(string phone, string message) { Console.WriteLine($"已調用API給{phone}發送消息:{message}"); } } public enum SmsSenderType { /// <summary> /// 控制台發送簡訊 /// </summary> Console, /// <summary> /// 通過HttpApi進行發送簡訊 /// </summary> HttpAPi } public interface ISmsSenderFactory { ISmsSender Create(); } public class SmsSenderFactory : ISmsSenderFactory { private readonly IConfigProvider _configProvider; public SmsSenderFactory( IConfigProvider configProvider) { _configProvider = configProvider; } public ISmsSender Create() { // 簡訊發送者創建,從配置管理中讀取當前的發送方式,並創建實例 var smsConfig = _configProvider.GetSmsConfig(); switch (smsConfig.SmsSenderType) { case SmsSenderType.Console: return new ConsoleSmsSender(); case SmsSenderType.HttpAPi: return new HttpApiSmsSender(); default: return new HttpApiSmsSender(); } } } public class SmsConfig { public SmsSenderType SmsSenderType { get; set; } } public interface IConfigProvider { SmsConfig GetSmsConfig(); } public class ConfigProvider : IConfigProvider { private readonly SmsConfig _smsConfig = new SmsConfig { SmsSenderType = SmsSenderType.Console }; public SmsConfig GetSmsConfig() { // 此處直接使用了寫死的簡訊發送配置,實際項目中往往是通過配置讀取的方式,實現該配置的載入。 return _smsConfig; } } } }
簡要分析。相較於版本2,引入的工廠模式,實現了「簡訊發送方式選擇」邏輯的封裝。這樣改造之後,便可以不論是在生產環境還是開發環境,都能夠通過配置項的修改,實現簡訊發送方式的切換。
當然,在增加了程式靈活性的同時,也引入了更多的類和配置。
版本4,工廠方法模式
using Autofac; using System; using System.Linq; namespace Use_Dependency_Injection_With_Factory_Pattern { public static class Demo4 { public static void Run() { Console.WriteLine($"開始運行{nameof(Demo4)}"); var cb = new ContainerBuilder(); cb.RegisterType<UserDal>().As<IUserDal>(); cb.RegisterType<UserBll>().As<IUserBll>(); cb.RegisterType<SmsSenderFactory>().As<ISmsSenderFactory>(); cb.RegisterType<ConsoleSmsSenderFactoryHandler>() .As<ISmsSenderFactoryHandler>(); cb.RegisterType<SmsSenderFactoryHandler>() .As<ISmsSenderFactoryHandler>(); cb.RegisterType<ConfigProvider>().As<IConfigProvider>(); var container = cb.Build(); var userBll = container.Resolve<IUserBll>(); var login = userBll.Login("yueluo", "newbe"); Console.WriteLine(login); login = userBll.Login("newbe", "yueluo"); Console.WriteLine(login); Console.WriteLine($"結束運行{nameof(Demo4)}"); } public class UserBll : IUserBll { private readonly IUserDal _userDal; private readonly ISmsSenderFactory _smsSenderFactory; public UserBll( IUserDal userDal, ISmsSenderFactory smsSenderFactory) { _userDal = userDal; _smsSenderFactory = smsSenderFactory; } public bool Login(string phone, string password) { var re = _userDal.Exists(phone, password); if (re) { var smsSender = _smsSenderFactory.Create(); smsSender.Send(phone, "您已成功登錄系統"); } return re; } } public enum SmsSenderType { /// <summary> /// 控制台發送簡訊 /// </summary> Console, /// <summary> /// 通過HttpApi進行發送簡訊 /// </summary> HttpAPi } public interface ISmsSenderFactory { ISmsSender Create(); } public class SmsSenderFactory : ISmsSenderFactory { private readonly IConfigProvider _configProvider; private readonly ISmsSenderFactoryHandler[] _smsSenderFactoryHandlers; public SmsSenderFactory( IConfigProvider configProvider, ISmsSenderFactoryHandler[] smsSenderFactoryHandlers) { _configProvider = configProvider; _smsSenderFactoryHandlers = smsSenderFactoryHandlers; } public ISmsSender Create() { // 簡訊發送者創建,從配置管理中讀取當前的發送方式,並創建實例 var smsConfig = _configProvider.GetSmsConfig(); // 通過工廠方法的方式,將如何創建具體簡訊發送者的邏輯從這裡移走,實現了這個方法本身的穩定。 var factoryHandler = _smsSenderFactoryHandlers.Single(x => x.SmsSenderType == smsConfig.SmsSenderType); var smsSender = factoryHandler.Create(); return smsSender; } } /// <summary> /// 工廠方法介面 /// </summary> public interface ISmsSenderFactoryHandler { SmsSenderType SmsSenderType { get; } ISmsSender Create(); } #region 控制台發送簡訊介面實現 public class ConsoleSmsSenderFactoryHandler : ISmsSenderFactoryHandler { public SmsSenderType SmsSenderType { get; } = SmsSenderType.Console; public ISmsSender Create() { return new ConsoleSmsSender(); } } /// <summary> /// 調試·簡訊API /// </summary> public class ConsoleSmsSender : ISmsSender { public void Send(string phone, string message) { Console.WriteLine($"已給{phone}發送消息:{message}"); } } #endregion #region API發送簡訊介面實現 public class SmsSenderFactoryHandler : ISmsSenderFactoryHandler { public SmsSenderType SmsSenderType { get; } = SmsSenderType.HttpAPi; public ISmsSender Create() { return new HttpApiSmsSender(); } } /// <summary> /// 真·簡訊API /// </summary> public class HttpApiSmsSender : ISmsSender { public void Send(string phone, string message) { Console.WriteLine($"已調用API給{phone}發送消息:{message}"); } } #endregion public class SmsConfig { public SmsSenderType SmsSenderType { get; set; } } public interface IConfigProvider { SmsConfig GetSmsConfig(); } public class ConfigProvider : IConfigProvider { private readonly SmsConfig _smsConfig = new SmsConfig { SmsSenderType = SmsSenderType.Console }; public SmsConfig GetSmsConfig() { // 此處直接使用了寫死的簡訊發送配置,實際項目中往往是通過配置讀取的方式,實現該配置的載入。 return _smsConfig; } } } }
簡要說明。相對於版本3,採用了工廠方法模式。本質上,就是將「不同的類型如何創建簡訊發送API」的邏輯轉移到了ISmsSenderFactory的實現類中。實際項目中,往往可以將ISmsSenderFactory和ISmsSender的實現類放在不同的程式集中。而且後續如果要增加新的發送方式,只需要增加對應的實現類並且註冊即可,進一步增加了可擴展性。
版本5,結合Autofac的最終版本
using Autofac; using Autofac.Features.Indexed; using System; namespace Use_Dependency_Injection_With_Factory_Pattern { public static class Demo5 { public static void Run() { Console.WriteLine($"開始運行{nameof(Demo5)}"); var cb = new ContainerBuilder(); cb.RegisterModule<CoreModule>(); cb.RegisterModule<SmsCoreModule>(); cb.RegisterModule<ConsoleSmsModule>(); cb.RegisterModule<HttpApiSmsModule>(); var container = cb.Build(); var userBll = container.Resolve<IUserBll>(); var login = userBll.Login("yueluo", "newbe"); Console.WriteLine(login); login = userBll.Login("newbe", "yueluo"); Console.WriteLine(login); Console.WriteLine($"結束運行{nameof(Demo5)}"); } public class CoreModule : Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterType<UserDal>().As<IUserDal>(); builder.RegisterType<UserBll>().As<IUserBll>(); builder.RegisterType<ConfigProvider>().As<IConfigProvider>(); } } public class SmsCoreModule : Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterType<SmsSenderFactory>() .As<ISmsSenderFactory>(); } } public class ConsoleSmsModule : Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterType<ConsoleSmsSender>().AsSelf();// 此註冊將會使得 ConsoleSmsSender.Factory 能夠被注入 builder.RegisterType<ConsoleSmsSenderFactoryHandler>() .Keyed<ISmsSenderFactoryHandler>(SmsSenderType.Console); } } public class HttpApiSmsModule : Module { protected override void Load(ContainerBuilder builder) { base.Load(builder); builder.RegisterType<HttpApiSmsSender>().AsSelf(); builder.RegisterType<SmsSenderFactoryHandler>() .Keyed<ISmsSenderFactoryHandler>(SmsSenderType.HttpAPi); } } public class UserBll : IUserBll { private readonly IUserDal _userDal; private readonly ISmsSenderFactory _smsSenderFactory; public UserBll( IUserDal userDal, ISmsSenderFactory smsSenderFactory) { _userDal = userDal; _smsSenderFactory = smsSenderFactory; } public bool Login(string phone, string password) { var re = _userDal.Exists(phone, password); if (re) { var smsSender = _smsSenderFactory.Create(); smsSender.Send(phone, "您已成功登錄系統"); } return re; } } public enum SmsSenderType { /// <summary> /// 控制台發送簡訊 /// </summary> Console, /// <summary> /// 通過HttpApi進行發送簡訊 /// </summary> HttpAPi } public interface ISmsSenderFactory { ISmsSender Create(); } public class SmsSenderFactory : ISmsSenderFactory { private readonly IConfigProvider _configProvider; private readonly IIndex<SmsSenderType, ISmsSenderFactoryHandler> _smsSenderFactoryHandlers; public SmsSenderFactory( IConfigProvider configProvider, IIndex<SmsSenderType, ISmsSenderFactoryHandler> smsSenderFactoryHandlers) { _configProvider = configProvider; _smsSenderFactoryHandlers = smsSenderFactoryHandlers; } public ISmsSender Create() { // 簡訊發送者創建,從配置管理中讀取當前的發送方式,並創建實例 var smsConfig = _configProvider.GetSmsConfig(); // 通過工廠方法的方式,將如何創建具體簡訊發送者的邏輯從這裡移走,實現了這個方法本身的穩定。 var factoryHandler = _smsSenderFactoryHandlers[smsConfig.SmsSenderType]; var smsSender = factoryHandler.Create(); return smsSender; } } /// <summary> /// 工廠方法介面 /// </summary> public interface ISmsSenderFactoryHandler { ISmsSender Create(); } #region 控制台發送簡訊介面實現 public class ConsoleSmsSenderFactoryHandler : ISmsSenderFactoryHandler { private readonly ConsoleSmsSender.Factory _factory; public ConsoleSmsSenderFactoryHandler( ConsoleSmsSender.Factory factory) { _factory = factory; } public ISmsSender Create() { return _factory(); } } /// <summary> /// 調試·簡訊API /// </summary> public class ConsoleSmsSender : ISmsSender { public delegate ConsoleSmsSender Factory(); public void Send(string phone, string message) { Console.WriteLine($"已給{phone}發送消息:{message}"); } } #endregion #region API發送簡訊介面實現 public class SmsSenderFactoryHandler : ISmsSenderFactoryHandler { private readonly HttpApiSmsSender.Factory _factory; public SmsSenderFactoryHandler( HttpApiSmsSender.Factory factory) { _factory = factory; } public ISmsSender Create() { return _factory(); } } /// <summary> /// 真·簡訊API /// </summary> public class HttpApiSmsSender : ISmsSender { public delegate HttpApiSmsSender Factory(); public void Send(string phone, string message) { Console.WriteLine($"已調用API給{phone}發送消息:{message}"); } } #endregion public class SmsConfig { public SmsSenderType SmsSenderType { get; set; } } public interface IConfigProvider { SmsConfig GetSmsConfig(); } public class ConfigProvider : IConfigProvider { private readonly SmsConfig _smsConfig = new SmsConfig { SmsSenderType = SmsSenderType.Console }; public SmsConfig GetSmsConfig() { // 此處直接使用了寫死的簡訊發送配置,實際項目中往往是通過配置讀取的方式,實現該配置的載入。 return _smsConfig; } } } }
簡要說明。相對於版本4,該版本結合了較多Autofac所提供的特性。
- 使用了
Module,使得相關的類更加聚合,往往可以通過這種方式將相關的邏輯獨立在一個程式集中 - 使用了
Index<,>的方式進行註冊。這是Autofac提供了一種服務註冊方式,將相同介面的實現以不同的鍵進行註冊和實例。參考鏈接 - 使用了Autofac提供了
Delegate Factories特性。這樣在操作ConsoleSmsSender這樣的類時,便也可以使用依賴注入。參考鏈接
總結
通過上述程式碼的演變過程,簡要介紹了工廠模式和工廠方法模式在項目當中的使用方法,也是對依賴注入方法使用的進一步熟悉。
在使用設計模式和依賴注入的過程當中,不可避免的增加了更多的介面和實現類。讀者需要深入理解,各個版本之間的差異,已經後一個版本產生的原因。如果讀者能夠將每個版本的變化總結為「隔離了什麼變化,實現了什麼解耦」這樣一句話,那麼說明讀者已經理解的其中的原因。
實際項目之中也並非始終都要套用最終的複雜模式,開發者需要根據實際項目可能的變化因素自行考評模式使用的力度。

