我一直在研究.NET的各种依赖注入框架,因为我觉得我正在开发的项目会从中受益匪浅。虽然我认为我已经很好地掌握了这些框架的功能,但我仍然不清楚如何最好地将它们引入大型系统。大多数演示(可以理解)往往是具有一个或两个依赖关系的非常简单的类。
我有三个问题......
首先,您如何处理那些常见但无趣的依赖关系,例如ILog,IApplicationSettings,IPermissions,IAudit。对于每个类来说,在构造函数中将这些作为参数似乎有点过分了。在需要时使用DI容器的静态实例来获取它们会更好吗?
MyClass(ILog log, IAudit audit, IPermissions permissions, IApplicationSettings settings)
// ... versus ...
ILog log = DIContainer.Get<ILog>();
第二次,您如何处理可能使用的依赖项,但创建起来可能会很昂贵。示例 - 类可能依赖于ICDBurner接口,但不希望创建具体实现,除非实际使用了CD刻录功能。您是否在构造函数中将接口传递给工厂(例如ICDBurnerFactory),或者您是否再次采用静态方式直接获取DI Container并在需要时请求它?
第三,假设您有一个大型Windows窗体应用程序,其中顶级GUI组件(例如MainForm)可能是数百个子面板或模式窗体的父级,每个子窗格或模式窗体都可以有几个依赖。这是否意味着应该将MainForm设置为具有其子项的所有依赖项的超集作为依赖项?如果你这样做了,最终会不会创建一个巨大的自我膨胀怪物来构建你创建MainForm时可能需要的每一个类,在这个过程中浪费时间和记忆?
答案 0 :(得分:25)
好吧,虽然您可以按照其他答案中的说明执行此操作,但我相信对于您的示例,有更重要的事情需要回答,那就是您可能违反了SRP原则,并且该类具有许多依赖关系。
我在你的例子中会考虑在几个更连贯的类中分解这个类,并且因为它们的依赖关系的数量会下降。
尼古拉的SRP和DI法则
“任何超过3级的课程 应该质疑依赖性 SRP违规“
(为了避免冗长的回答,我在IoC and SRP博客文章中详细介绍了我的答案)
答案 1 :(得分:8)
首先:根据需要将简单依赖项添加到构造函数中。没有必要为每个构造函数添加每个类型,只需添加您需要的类型。需要另一个,只需展开构造函数。性能不应该是一件大事,因为大多数这些类型可能是单身人士,因此在第一次通话后已经创建。不要使用静态DI容器来创建其他对象。而是将DI容器添加到自身,以便它可以将自身解析为依赖关系。所以这样的事情(暂时假设Unity)
IUnityContainer container = new UnityContainer();
container.RegisterInstance<IUnityContainer>(container);
这样您就可以在IUnityContainer上添加一个依赖项,并使用它来创建昂贵或很少需要的对象。主要优点是单元测试更容易,因为没有静态依赖。
第二:无需传入工厂类。使用上述技术,您可以使用DI容器本身在需要时创建昂贵的对象。
三:将DI容器和轻型单件依赖项添加到主窗体,并根据需要通过DI容器创建其余部分。需要更多代码,但正如您所说,如果您在启动时创建所有内容,主窗体的启动成本和内存消耗将会达到顶峰。
答案 2 :(得分:4)
<强>首先强>
您可以在需要时将这些对象作为成员而不是在构造函数中注入。这样,您不必在使用情况发生变化时对构造函数进行更改,也不需要使用静态。
<强>第二强>
通过某种建造者或工厂。
<强>第三强>
任何类都应该只拥有它本身需要的依赖项。应该为子类注入自己的特定依赖项。
答案 3 :(得分:4)
我有一个类似的案例与“昂贵的创建和可能使用”有关,在我自己的IoC实现中,我正在为工厂服务添加自动支持。
基本上,而不是:
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<T>"/> 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<T>"/> 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
}
}
基本上,当我从服务容器构建器类构建服务容器时,所有服务注册都会自动获得另一个协同服务,为该服务实现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刻录机服务。由于以下原因,这很有用:
这两个是同时出现的:
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();
}
答案 4 :(得分:2)
<强>首先强>
你可以通过创建一个容器来保存你的“无趣的”依赖关系(ILog,ICache,IApplicationSettings等),并使用构造函数注入来注入它,然后在构造函数内部,从容器中保存服务的字段。 .Resolve()?我不确定我是否喜欢这样,但是,这是一种可能性。
或者,您可能希望使用新的IServiceLocator通用接口(http://blogs.msdn.com/gblock/archive/2008/10/02/iservicelocator-a-step-toward-ioc-container-service-locator-detente.aspx)而不是注入依赖项?
<强>第二强>
您可以将setter注入用于可选/按需依赖项吗?我想我会按需要注入工厂和新工厂。
答案 5 :(得分:0)
要部分回答我的第一个问题,我刚刚找到了Jeremy Miller的blog post,展示了如何使用Structure Map和setter注入来自动填充你的公共属性对象。他以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; }
}
将在构造时自动设置其Logger属性:
container.GetInstance<ClassWithLogger>();