【问题标题】:IoC with static and dynamic dependencies具有静态和动态依赖关系的 IoC
【发布时间】:2011-12-07 06:10:30
【问题描述】:

我正在尝试在我的应用中实现 IoC。我有这个模型:

interface IService;
interface IComponent;

class Service : IService
    Service()

class Component : IComponent
    Component(IService service, object runtimeValue) { }

在我的应用程序中的某个时间点,我需要获得IComponent。我的应用程序使用 IoC 容器 (Unity)。我可以在容器中注册Service,但我不能为它的依赖关系runtimeValueComponent b/c 做同样的事情。根据this,我必须使用工厂并在我需要的任何地方注入IComponent

interface IComponentFactory
     IComponent CreateComponent(object runtimeValue)

class ComponentProvider : IComponentProvider
     ComponentProvider(IComponentFactory factory) { }

     IComponent CreateAndCacheComponent(object runtimeValue) {
         _component = factory.CreateComponent(runtimeValue)
         return _component
     }

     // other methods

我必须能够向容器注册工厂,因此它必须只有静态依赖项。同时它必须能够提供创建组件所需的IService 类型的服务实例。
这是工厂实现。我唯一能想到的就是使用Func<> 委托作为依赖:

class ComponentFactory : IComponentFactory
    ComponentFactory(Func<IService> serviceFactoryDelegate)

    IComponent CreateComponent(object runtimeValue) {
        return new Component(serviceFactoryDelegate.Invoke(), runtimeValue)
    }

...并将代理注册到容器中作为静态工厂,以便它回调容器来解析服务(我在 .net 2.0 上使用 Unity 1.2):

Container
    .Configure<IStaticFactoryConfiguration>()
    .RegisterFactory<Func<IService>>(container => (Func<IService>)container.Resolve<IService>)

现在我可以使用容器来解析 ComponentProvider 并根据运行时值获取组件:

// this happens inside CompositionRoot
provider = Container.Resovle<IComponentProvider>()
component = provider.CreateAndCacheComponent("the component")

现在我对此有一些疑问:

  1. 工厂打电话给new Component(...) 我很不高兴。这不是穷人的DI吗?

  2. 在工厂的构造函数上使用Func&lt;IService&gt; 时好莱坞原则是否仍然有效?我的意思是,它最终调用 container.Resolve... 有点像 SL。唯一的区别是代码在应用的容器注册部分,而不是在工厂类中。

  3. 就 DI 和 IoC 而言,此实现是否有任何(其他)问题?

【问题讨论】:

  • 为什么需要委托?为什么不能直接注入IService
  • 为什么需要Func&lt;IService&gt;?让你的容器解决这个问题不是重点吗?
  • @Daniel, @Jay:注入服务假定将使用相同的实例来创建组件。如果工厂有另一个方法必须创建具有IService 依赖项的不同对象,那么两个方法中将使用相同的服务实例。使用委托允许容器在每次需要时解析服务,使用其配置的生命周期。
  • 您通常不应该关心您的服务的生命周期,或者您是否获得与另一个消费者相同的实例。您真的需要不同的服务实例吗?
  • 最后我遇到了其他人使用这样的 Func:nblumhardt.com/2010/01/the-relationship-zoo: 如果 A 需要创建 B 的实例,那么它需要一个 Func (我假设它会回调容器)

标签: c# dependency-injection inversion-of-control unity-container ioc-container


【解决方案1】:
  1. 不,不是。工厂的全部目的是创建具体类的实例。
  2. 基本上,是的,但正如我在评论中已经问过的,我不明白为什么这是必要的。您可以直接注入IService 的实例
  3. 它比它需要的要复杂一些。为什么双重重定向IComponentProvider -> IComponentFactory?看起来IComponentFactory 并没有带来任何好处。

    像这样实现ComponentProvider

    class ComponentProvider : IComponentProvider
    {
        ComponentProvider(IService service) { _service = service; }
    
        IComponent CreateAndCacheComponent(object runtimeValue) {
            _component = new Component(_service, runtimeValue);
            return _component;
    }
    

    这将为您带来以下好处:

    1. 您摆脱了不必要的接口IComponentFactory 以及相应的实现。
    2. IService无需注册工厂

一般来说,你如何实现它取决于你真正需要什么:

"runtimeValue" 在整个运行时可以是相同的,例如从设置中读取的连接字符串。在这种情况下,不需要工厂/提供者,您可以简单地新建实例并将其注册到容器中。每个需要IComponent 的人都在构造函数而不是提供程序中请求一个。

如果“runtimeValue”在对 CreateAndCacheComponent 的调用之间确实发生变化,您将只实现一个工厂并将其作为依赖项传递。

【讨论】:

  • @Daniel 我担心IComponentProviderIComponentFactory 之间会有一些混淆...提供者应该有其他方法并且依赖于工厂以创建一个零件。提供者是需要组件实例的对象,只能在运行时创建。
  • @Daniel 恐怕 runtimeValue 在调用之间真的会发生变化。该组件是一个短暂的对象,我知道我不应该在容器中注册它。然而,工厂,我使用Container.RegisterType&lt;IComponentFactory, ComponentFactory&gt;(new ContainerControlledLifetimeManager()) 将它注册为一个实例
  • @Suiden:没关系。在这种情况下,你真的需要一个工厂。所以继续使用它。
【解决方案2】:
  1. 与Poor Man's DI 相比,这是一个很大的进步,但如果您不必在每次将新的依赖项添加到组件的构造函数时都更改此工厂方法,那就太好了。
  2. 本身不是问题。把它想象成你在注入一个匿名工厂类。它仍然可以被模拟以进行单元测试,并且可以更改绑定,因此您仍然可以获得 DI 的好处。但它是一个额外的抽象层,可能不是必需的。在这种情况下,您仍然可以通过将IService 直接注入工厂而不是Func 来避免它。
  3. 通常在使用依赖注入时,您希望注入 services 而不是 values。您发现必须同时拥有两者的事实可能表明您需要重新考虑您的类的 API。例如,也许您应该将值传递给类的方法,而不是构造函数。如果不了解更多细节,很难说最好的方法是什么。

【讨论】:

  • 好吧,这段代码不是一个真实的例子,我不得不承认。但是让我们说,为了好玩,runtimeValue 真的是组件的必需依赖项,这意味着没有它组件就没有意义。所以,我觉得它很自然地出现在 ctor 身上。我不应该将其视为依赖项吗?
  • @Suiden:在构造函数中混合服务和值通常指向类的问题,例如违反 SRP。这就是为什么你应该仔细看看。如果仔细检查后仍然认为正确,可以将它们混合在ctor中。
【解决方案3】:

对于问题1:在工厂里打电话new没有错。您已将实例化隔离到应用程序中的一个位置;你只是把那个地方放在工厂而不是容器上。

如果您需要模拟或更改实现,您只需模拟或更改工厂实现,而不是单独的组件。

【讨论】:

  • 如果我能在容器上做到这一点并完全摆脱工厂,我会很高兴。工厂的存在只是因为那个运行时值。您认为有办法在容器上执行此操作吗?
猜你喜欢
  • 2011-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多