代码之家  ›  专栏  ›  技术社区  ›  Mark Heath

对具有多个依赖项的类使用依赖项注入框架

  •  36
  • Mark Heath  · 技术社区  · 17 年前

    我一直在研究.NET的各种依赖注入框架,因为我觉得我正在研究的项目将从中受益匪浅。当我认为我很好地掌握了 能力 在这些框架中,我仍然不清楚如何最好地将它们引入大型系统。大多数演示(可以理解)趋向于具有一个或两个依赖项的非常简单的类。

    我有三个问题…

    弗斯特 ,如何处理这些常见但无趣的依赖项,例如iLog、iApplicationSettings、iPermissions和iAudit。在每个类的构造函数中使用这些作为参数似乎都有点过分。当需要DI容器时,最好使用其静态实例来获取这些实例吗?

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

    第二 ,如何处理 可以 可以使用,但创建成本可能很高。示例-类可能依赖于ICDBburner接口,但不希望创建具体的实现,除非实际使用了CD刻录功能。您是将接口传递给构造函数中的工厂(例如icdburnerFactory),还是再次使用某种静态方法直接访问DI容器并在需要时请求它?

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

    6 回复  |  直到 10 年前
        1
  •  8
  •   Maurice    17 年前

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

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

    通过这种方式,您只需添加对IUnitycontainer的依赖,并使用它来创建昂贵或很少需要的对象。其主要优点是,在单元测试时,由于没有静态依赖关系,因此更加容易。

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

    三: 将DI容器和轻单体依赖项添加到主窗体,并根据需要通过DI容器创建其余的依赖项。需要更多的代码,但是正如您所说的,如果您在启动时创建所有东西,那么主窗体的启动成本和内存消耗将非常困难。

        2
  •  25
  •   micahtan    10 年前

    好吧,虽然您可以像其他答案中描述的那样这样做,但我相信对于您的示例,还有更重要的事情需要回答,那就是您可能违反了SRP原则,类具有许多依赖性。

    在您的示例中,我将考虑将类分解为两个更为一致、关注点更为集中的类,这样它们的依赖项的数量就会下降。

    尼古拉定律

    “任何超过3人的班级 应询问依赖项 SRP违规

    (为了避免冗长的回答,我在 IoC and SRP 博客帖子)

        3
  •  4
  •   wsorenson    17 年前

    第一:

    如果需要,可以将这些对象作为成员而不是构造函数中的成员来注入。这样,您就不必随着使用情况的变化而对构造函数进行更改,也不需要使用静态的。

    第二:

    把一些建筑工人或工厂交给他们。

    第三:

    任何类都应该只有它自己需要的依赖项。子类应该注入它们自己特定的依赖关系。

        4
  •  4
  •   Lasse V. Karlsen    16 年前

    我有一个类似的案例与“创造和 可以 在我自己的IOC实现中,我添加了对工厂服务的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
        }
    }
    

    基本上,当我从ServiceContainerBuilder类构建服务容器时,所有服务注册都会自动地被授予另一个联合服务,为该服务实现IServiceFactory,除非程序员已经在他/她自己身上显式注册了该服务。然后使用上面的服务,其中一个参数指定策略(如果不使用策略,则可以为空)。

    这使我能够做到:

    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刻录机对象。在上面的代码中,我将解析服务,而不是当然,但它是一个发生了什么的示例。

    因此,使用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();
    }
    
        5
  •  2
  •   Peter Mounce    17 年前

    第一:

    您可以通过创建一个容器来保存您的“无趣”依赖项(ilog、icache、IApplicationSettings等),然后使用构造函数注入,然后在构造函数内部,从container.resolve()对服务字段进行水合物化处理。我不确定我会喜欢,但是,嗯,这是可能的。

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

    第二:

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

        6
  •  0
  •   Mark Heath    17 年前

    部分回答我的 第一 问题,我刚发现 blog post JeremyMiller展示了如何使用结构图和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; }
    }
    

    将在构造时自动设置其记录器属性:

    container.GetInstance<ClassWithLogger>();
    
    推荐文章