36

我一直在研究 .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 容器并在需要时请求它?

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

4

6 回答 6

26

好吧,虽然您可以按照其他答案中的描述执行此操作,但我相信关于您的示例还有更重要的事情需要回答,那就是您可能违反了具有许多依赖关系的类的 SRP 原则。

在您的示例中,我会考虑将类分解为几个具有重点关注点的更连贯的类,因此它们的依赖项的数量会下降。

尼古拉的 SRP 和 DI 定律

“任何具有超过 3 个依赖项的类都应因违反 SRP 而受到质疑”

(为了避免冗长的回答,我在IoC 和 SRP博客文章中详细发布了我的答案)

于 2009-10-16T09:14:42.110 回答
6

首先:根据需要将简单的依赖项添加到您的构造函数中。无需将每种类型都添加到每个构造函数中,只需添加您需要的类型即可。需要另一个,只需扩展构造函数即可。性能应该不是什么大事,因为这些类型中的大多数可能是单例,因此在第一次调用后就已经创建了。不要使用静态 DI 容器来创建其他对象。而是将 DI 容器添加到自身,以便它可以将自身解析为依赖项。所以像这样的东西(暂时假设 Unity)

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

这样,您只需添加对 IUnityContainer 的依赖项并使用它来创建昂贵或很少需要的对象。主要优点是单元测试时更容易,因为没有静态依赖项。

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

三:将DI容器和轻单例依赖添加到主窗体中,根据需要通过DI容器创建其余部分。需要更多代码,但正如您所说,如果您在启动时创建所有内容,则主窗体的启动成本和内存消耗将达到顶峰。

于 2008-10-13T20:27:24.120 回答
4

第一的:

您可以在需要时将这些对象作为成员而不是在构造函数中注入。这样,您不必在使用更改时对构造函数进行更改,并且您也不需要使用静态。

第二:

进入某种建筑商或工厂。

第三:

任何类都应该只具有它本身需要的那些依赖项。子类应该注入它们自己的特定依赖项。

于 2008-10-13T19:35:57.827 回答
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&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
    }
}

基本上,当我从我的服务容器构建器类构建服务容器时,所有服务注册都会自动获得另一个协同服务,为该服务实现 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();
}
于 2009-10-19T16:02:05.603 回答
2

第一的:

您可以通过创建一个容器来保存您的“无趣”依赖项(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 注入用于可选/按需依赖项吗?我想我会去注入工厂并按需从那里新建。

于 2008-10-12T12:13:31.310 回答
0

为了部分回答我的第一个问题,我刚刚找到了Jeremy Miller 的一篇博文,展示了如何使用结构映射和设置器注入来自动填充对象的公共属性。他以 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>();
于 2008-10-14T08:41:00.083 回答