将dependency injection框架用于具有许多依赖关系的类

我一直在寻找.NET的各种dependency injection框架,因为我感觉我正在从事的这个项目将从中受益匪浅。 虽然我认为自己对这些框架的能力有了很好的把握,但我还是不清楚如何最好地将它们引入大型系统。 大多数演示(可以理解)往往是相当简单的类有一个或两个依赖关系。

我有三个问题

首先 ,你如何处理那些常见但无趣的依赖,如ILog,IApplicationSettings,IPermissions,IAudit。 对于每个类来说,将它们作为构造函数的参数看起来有点过分。 在需要的时候使用DI容器的静态实例来获取它们会更好吗?

MyClass(ILog log, IAudit audit, IPermissions permissions, IApplicationSettings settings) // ... versus ... ILog log = DIContainer.Get<ILog>(); 

其次 ,你如何处理可能使用的依赖关系,但创build起来可能会很昂贵。 示例 – 类可能对ICDBurner接口有依赖关系,但不希望创build具体实现,除非实际使用CD刻录function。 你是否在构造函数中传入了工厂的接口(例如ICDBurnerFactory),还是直接通过一些静态的方式直接进入DI Container,并在需要的时候请求它?

第三 ,假设你有一个大型的Windows窗体应用程序,其中最高级别的GUI组件(例如MainForm)是潜在的数百个子面板或模态窗体的父项,每个子窗体可能有几个依赖关系。 这是否意味着MainForm应该被设置为依赖关系的子集的所有依赖关系的超集? 如果你这样做,那么最终不会创build一个巨大的自我膨胀的怪物,它构build了你创buildMainForm时所需要的每一个类,在这个过程中浪费了时间和内存?

首先:根据需要将简单的依赖项添加到您的构造函数中。 不需要为每个构造函数添加每个types,只需添加所需的types即可。 需要另一个,只需展开构造函数。 性能不应该是一件大事,因为这些types中的大多数可能是在第一次调用之后已经创build的单例。 不要使用静态DI容器来创build其他对象。 而是将DI容器添加到自身,以便可以将其自身parsing为依赖项。 所以像这样的事情(假设目前统一)

 IUnityContainer container = new UnityContainer(); container.RegisterInstance<IUnityContainer>(container); 

通过这种方式,您可以添加对IUnityContainer的依赖关系,并使用它创build昂贵的或不常用的对象。 主要优点是unit testing时没有静态依赖关系,因此更容易。

第二:不需要通过工厂class级。 使用上面的技术,您可以使用DI容器本身在需要时创build昂贵的对象。

三:将DI容器和轻单独依赖关系添加到主表单中,并根据需要通过DI容器创build其余部分。 需要多一点的代码,但正如你所说的,如果你在启动时创build所有的东西,那么mainform的启动成本和内存消耗将通过屋顶。

那么,虽然你可以做到这一点,如其他答案中所述,我认为有关你的例子更重要的是要回答,那就是你可能违反SRP的原则与类有很多依赖。

在我的例子中,我将考虑的是以更为连贯的课程将课堂分解为集中的关注点,因此它们的依赖关系的数量将会下降。

尼古拉的SRP和DI的定律

“任何具有超过3个依赖关系的类别都应该被质疑SRP违规”

(为了避免冗长的回答,我详细地发布了IoC和SRP博客post的答案)

第一:

您可以在需要时将这些对象作为成员而不是构造函数注入。 这样你就不必随着用法的改变而改变构造函数,而且你也不需要使用静态的。

第二:

通过某种build筑工人或工厂。

第三:

任何类只能拥有它本身所需要的依赖关系。 子类应该注入自己特定的依赖关系。

我有一个类似的案例涉及“昂贵的创build和可能使用”,在我自己的IoC实施中,我join了对工厂服务的automagic支持。

基本上,而不是这个:

 public SomeService(ICDBurner burner) { } 

你会这样做:

 public SomeService(IServiceFactory<ICDBurner> burnerFactory) { } ICDBurner burner = burnerFactory.Create(); 

这有两个好处:

  • 在幕后,解决你的服务的服务容器也被用于解决燃烧器,如果和当它被请求
  • 这样可以减轻我以前看到过的这种情况,其中典型的方法是将服务容器本身作为参数注入到服务中,基本上说“这个服务需要其他服务,但是我不会轻易的告诉你哪些“

工厂对象很容易制作,解决了很多问题。

这是我的工厂类:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using LVK.IoC.Interfaces; using System.Diagnostics; namespace LVK.IoC { /// <summary> /// This class is used to implement <see cref="IServiceFactory{T}"/> for all /// services automatically. /// </summary> [DebuggerDisplay("AutoServiceFactory (Type={typeof(T)}, Policy={Policy})")] internal class AutoServiceFactory<T> : ServiceBase, IServiceFactory<T> { #region Private Fields [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly String _Policy; #endregion #region Construction & Destruction /// <summary> /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class. /// </summary> /// <param name="serviceContainer">The service container involved.</param> /// <param name="policy">The policy to use when resolving the service.</param> /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception> public AutoServiceFactory(IServiceContainer serviceContainer, String policy) : base(serviceContainer) { _Policy = policy; } /// <summary> /// Initializes a new instance of the <see cref="AutoServiceFactory&lt;T&gt;"/> class. /// </summary> /// <param name="serviceContainer">The service container involved.</param> /// <exception cref="ArgumentNullException"><paramref name="serviceContainer"/> is <c>null</c>.</exception> public AutoServiceFactory(IServiceContainer serviceContainer) : this(serviceContainer, null) { // Do nothing here } #endregion #region Public Properties /// <summary> /// Gets the policy that will be used when the service is resolved. /// </summary> public String Policy { get { return _Policy; } } #endregion #region IServiceFactory<T> Members /// <summary> /// Constructs a new service of the correct type and returns it. /// </summary> /// <returns>The created service.</returns> public IService<T> Create() { return MyServiceContainer.Resolve<T>(_Policy); } #endregion } } 

基本上,当我从服务容器构build器类构build服务容器时,除非程序员明确注册了服务,否则所有服务注册都会自动给予另一个协同服务,为该服务实现IServiceFactory。 然后使用上述服务,一个参数指定策略(如果不使用策略,则可以为null)。

这允许我这样做:

 var builder = new ServiceContainerBuilder(); builder.Register<ISomeService>() .From.ConcreteType<SomeService>(); using (var container = builder.Build()) { using (var factory = container.Resolve<IServiceFactory<ISomeService>>()) { using (var service = factory.Instance.Create()) { service.Instance.DoSomethingAwesomeHere(); } } } 

当然,更典型的用法是使用CD Burner对象。 在上面的代码中,我会解决服务,而不是当然,但这是一个发生了什么事情的例证。

所以,你的CD刻录机服务,而不是:

 var builder = new ServiceContainerBuilder(); builder.Register<ICDBurner>() .From.ConcreteType<CDBurner>(); builder.Register<ISomeService>() .From.ConcreteType<SomeService>(); // constructor used in the top of answer using (var container = builder.Build()) { using (var service = container.Resolve<ISomeService>()) { service.Instance.DoSomethingHere(); } } 

在服务内部,你现在可以有一个服务,一个工厂服务,知道如何根据请求解决你的CD刻录机服务。 这是有用的,原因如下:

  • 您可能需要同时解决多个服务(同时刻录两张光盘)?
  • 你可能不需要它,创build它可能是昂贵的,所以你只需要解决它
  • 您可能需要多次解决,处置,解决,处置,而不是希望/试图清理现有的服务实例
  • 你也在你的构造函数中标记哪些服务是你需要的 ,哪些是你需要的

这里有两个在同一时间:

 using (var service1 = container.Resolve<ISomeService>()) using (var service2 = container.Resolve<ISomeService>()) { service1.Instance.DoSomethingHere(); service2.Instance.DoSomethingHere(); } 

这里是两个接一个,不重复使用相同的服务:

 using (var service = container.Resolve<ISomeService>()) { service.Instance.DoSomethingHere(); } using (var service = container.Resolve<ISomeService>()) { service.Instance.DoSomethingElseHere(); } 

第一:

你可以通过创build一个容器来保存你的“无趣的”依赖项(ILog,ICache,IApplicationSettings等等),然后使用构造函数注入来注入它,然后在构造函数的内部为container.Resolve提供服务的字段)? 我不确定我会喜欢那个,但是,这是可能的。

或者,您可能想使用新的IServiceLocator公共接口( http://blogs.msdn.com/gblock/archive/2008/10/02/iservicelocator-a-step-toward-ioc-container-service-locator-detente .aspx )而不是注入依赖?

第二:

您可以使用setter注入的可选/按需依赖? 我想我会去注射工厂,并从那里按需提供新产品。

为了部分回答我的第一个问题,我刚刚发现了一篇由Jeremy Miller撰写的博客文章 ,展示了Structure Map和setter injection如何用于自动填充对象的公共属性。 他以ILogger为例:

 var container = new Container(r => { r.FillAllPropertiesOfType<ILogger>().TheDefault.Is .ConstructedBy(context => new Logger(context.ParentType)); }); 

这意味着任何具有ILogger属性的类,例如:

 public class ClassWithLogger { public ILogger Logger { get; set; } } public class ClassWithLogger2 { public ILogger Logger { get; set; } } 

将在构build时自动设置Logger属性:

 container.GetInstance<ClassWithLogger>();