抽象工厂的依赖注入






4.44/5 (8投票s)
引言
像 Jeffrey Palermo 的洋葱架构这样的应用程序模型基于控制反转。依赖注入是实现它的常用方法。
用于介绍该技术的经典示例是通过构造函数将基础设施存储库注入到业务类中
public BusinessClassA { private readonly IRepositoryA _repositoryA; public BusinessClassA(IRepositoryA repositoryA) { _repositoryA = repository; } […] }
但是,在实际项目中,对每个基础设施服务应用此方法存在一些限制
业务类可能需要更多基础设施存储库/代理/服务,并且构造函数可能会爆炸:ctor(IRepositoryA repositoryA, IRepositoryB repositoryB, IProxyC proxyC, ….);
基础设施服务实例的生命周期应独立于业务类的生命周期进行管理。 在示例中不会发生这种情况,因为存储库是一个私有字段并且只初始化一次;
本文建议注入一个工厂而不是直接注入基础设施服务,以解决这两个问题。
背景
抽象工厂模式
“为创建相关或依赖对象的系列提供一个接口,而无需指定它们的具体类。”控制反转
“不要打电话给我们,我们会打电话给你(好莱坞原则)”。通常,如果 A 类型的类使用 B 类型的类(功能依赖性流),则 B 需要在 A 之前编译。
但是,当我们为 B 类引入一个接口,将其放入 A 的组件中,并将 B 的具体实现的创建委托给外部工厂容器时,A 可以在 B 之前编译。
这样,源代码依赖流与功能依赖流相反(这是术语“反转”的含义)。
可以通过两种方式实现控制反转
依赖注入(构造函数、参数、setter 或接口注入):使用构建器对象来初始化对象,并提供所需的依赖项,以便从类外部“注入”依赖项。
服务定位器:引入一个定位器对象,用于“解析”类中的依赖项。
示例中的建议解决方案
考虑一个使用洋葱架构建模的简单应用程序。 该应用程序管理一所学校。 考虑这样一种场景,我们需要向所有学校成员批量通知有关圣诞节假期的事情。
演示应用程序的模型由学校的参与者组成:学生和培训师。
namespace Tiskali.Core.Model { public abstract class Person { public virtual Guid ID { get; set; } public virtual string PIN { get; set; } public virtual string Name { get; set; } public virtual string Surname { get; set; } public virtual string Email { get; set; } } }
namespace Tiskali.Core.Model { public partial class Student : Person { } }
namespace Tiskali.Core.Model { public partial class Trainer : Person { } }
用例模块的场景作为一个事务脚本封装在应用程序服务中。 该服务需要两个基础设施服务:一个存储库(为了检索系统中注册的所有人员,这些人员是通知的目标)和一个通知引擎。
按需获取通知引擎是一个关键点,因为它的生命周期变得可管理。 我们可以假设通知引擎包装了 System.Net.Mail.SmtpClient,它是可释放的。 按需获取服务让我们使用 using 构造来释放它。
namespace Tiskali.Core.Services { /// <summary> /// Use Case Module: Notification of events /// </summary> public class NotificationService : INotificationService { #region Private fields private readonly IServiceFactory _factory; #endregion /// <summary> /// Ctor /// </summary> /// <param name="factory">Abstract factory</param> public NotificationService(IServiceFactory factory) { _factory = factory; // IoC by Dependency Injection } /// <summary> /// Notify a closure /// </summary> /// <param name="info">Infos about closure</param> public void NotifySchoolClosure(ClosureInfo info) { var personRepository = _factory.CreatePersonRepository(); // IoC by Service Locator var people = personRepository.GetAllActivePeople(); NotifySchoolClosure(info, people); } private void NotifySchoolClosure(ClosureInfo info, IEnumerable<Person> people) { using (var notificator = _factory.CreateEmailNotificator()) { foreach (var person in people) { notificator.SendNotification(person.Email, FormatMessage(person, info)); } } } private string FormatMessage(Person person, ClosureInfo info) { var messageBuilder = new StringBuilder(); messageBuilder.Append(string.Format("Hi {0}. ", person.Name)); messageBuilder.Append(info.Reason); messageBuilder.Append(string.Format(" The school will be close from {0} to {1}", info.StartDate.Date, info.EndDate)); messageBuilder.Append("Best regards."); return messageBuilder.ToString(); } } }
以下是基础设施服务的接口。
namespace Tiskali.Core.Repository { /// <summary> /// Repository of people /// </summary> public interface IPersonRepository { /// <summary> /// Gets all currently active people /// </summary> /// <returns>All currently people</returns> IEnumerable<Person> GetAllActivePeople(); } } namespace Tiskali.Core.Notificators { /// <summary> /// Notificator service /// </summary> public interface IEmailNotificator : IDisposable { /// <summary> /// Sends a notification (message) to a recipient (toAddress) /// </summary> /// <param name="toAddress">Recipient address</param> /// <param name="message">Contents of the notification</param> void SendNotification(string toAddress, string message); } }
在这两个接口的具体的实现里,在这个简单的演示中是空的。
namespace Tiskali.Infrastructure.Repository { /// <summary> /// Repository of people /// </summary> public class PersonRepository : IPersonRepository { private static ICollection<Person> _people = new List<Person>() { new Student { ID = Guid.NewGuid(), Name = "Simona", Surname = "Bianchi", Email = "simona.bianchi@tiskali.it", PIN = "00001" }, new Student { ID = Guid.NewGuid(), Name = "Marco", Surname = "Rossi", Email = "marco.rossi@tiskali.it", PIN = "01001" }, new Student { ID = Guid.NewGuid(), Name = "Paolo", Surname = "Verdi", Email = "paolo.verdi@tiskali.it", PIN = "01002" }, }; /// <summary> /// Gets all currently active people /// </summary> /// <returns>All currently people</returns> public IEnumerable<Person> GetAllActivePeople() { return _people.AsEnumerable(); } } } namespace Tiskali.Infrastructure.Notificators { /// <summary> /// Represents a concrete implementor of a email notificator /// </summary> public class EmailNotificator : IEmailNotificator { /// <summary> /// Sends a notification (message) to a recipient (toAddress) /// </summary> /// <param name="toAddress">Recipient address</param> /// <param name="message">Contents of the notification</param> public void SendNotification(string toAddress, string message) { // Here send the email with System.Net.Mail.SmtpClient } public void Dispose() { // Here dispose System.Net.Mail.SmtpClient } } }
抽象工厂被分解为更多接口,以便充分利用接口隔离原则。
namespace Tiskali.Core.Factories { /// <summary> /// Abstract Factory for notification engine /// </summary> public interface IEmailNotificatorFactory { IEmailNotificator CreateEmailNotificator(); } } namespace Tiskali.Core.Factories { /// <summary> /// Abstract Factory for repositories /// </summary> public interface IRepositoryFactory { IPersonRepository CreatePersonRepository(); } } namespace Tiskali.Core.Factories { /// <summary> /// Abstract factory for all infrstructure services /// that are required by the core project /// </summary> public interface IServiceFactory : IRepositoryFactory, IEmailNotificatorFactory { } }
具体工厂在单独的项目中定义。
namespace Tiskali.Factories.Concrete { public class ServiceFactory : IServiceFactory { public virtual IPersonRepository CreatePersonRepository() { return new PersonRepository(); } public virtual IEmailNotificator CreateEmailNotificator() { return new EmailNotificator(); } } }
该应用程序由一个简单的控制台应用程序运行。
namespace Tiskali.ConsoleApplication { class Program { public Program() { RootContainer.RegisterAll(); // Forces IoC conteiner to register dependencies } static void Main(string[] args) { NotifyChristmasVacation(); //Executes a scenario of the Use Case Module Console.WriteLine("Press any key to terminate the program..."); Console.ReadKey(); } static void NotifyChristmasVacation() { INotificationService notificationService = RootContainer.Resolve<INotificationService>(); var closureInfo = new ClosureInfo { StartDate = new DateTime(DateTime.Now.Year, 12, 24), EndDate = new DateTime(DateTime.Now.Year, 12, 26), Reason = "Christmas vacation." }; notificationService.NotifySchoolClosure(closureInfo); } } }
组合根发生在 RootContainer 类中。
namespace Tiskali.ConsoleApplication { static class RootContainer { #region Private field private readonly static UnityContainer _container; #endregion /// <summary> /// Static ctor /// </summary> static RootContainer() { _container = new UnityContainer(); RegisterAll(); } internal static void RegisterAll() { _container.RegisterType<IEmailNotificatorFactory, ServiceFactory>(); _container.RegisterType<IRepositoryFactory, ServiceFactory>(); _container.RegisterType<IServiceFactory, ServiceFactory>(); _container.RegisterType<INotificationService, NotificationService>(); } internal static IofS Resolve<IofS>() { return _container.Resolve<IofS>(); } } }
使用代码
该代码以包含四个项目的单个解决方案 (VS2013) 交付
Tiskali.Core:它包含业务逻辑作为事务脚本,贫血模型; 抽象工厂的定义和基础设施服务的接口;
Tiskali.Infrastructure:它包含代理、存储库或任何技术相关类的具体实现;
Tiskali.Factories:它包含抽象工厂的具体实现;
Tiskali.ConsoleApplication:它运行应用程序;
历史
2014-05-26:第一个版本。