【问题标题】:How to Avoid Coupling with an IoC Container如何避免与 IoC 容器耦合
【发布时间】:2011-08-12 23:22:12
【问题描述】:

我正在使用 DI 和 IoC 开发可扩展框架。用户必须能够通过将自己的实现放入容器中来覆盖框架内的现有功能。

我怎样才能允许用户在不知道我正在使用哪个 IoC 容器的情况下执行此操作?

我目前的中途解决方案是按如下方式构建我的程序集:

1) 定义只包含接口的抽象程序集。

2) 定义实现这些接口的具体程序集。用户可以定义自己的来覆盖现有的功能。

3) 在单独的程序集中定义容器绑定;即每个混凝土组件一个绑定组件。

这意味着具体组件不与特定的 IoC 容器耦合,如果我使用不同的容器,它们将被关闭以防止更改。但是,用户仍然需要知道我的框架正在使用哪个容器来编写绑定程序集,如果我更改了 IoC 容器(即从 Ninject 到 Spring),他们将需要发布新的绑定程序集。

我错过了什么吗?

【问题讨论】:

  • 这种方法的问题是你受限于容器可以支持的最小公分母,在很多情况下这不能满足你的内部需求。我已经尝试过这种确切的方法,但它会给刚接触该项目的用户带来太多的困惑。
  • 你用什么代替?
  • 该框架提供了一组显式的注册点,并在没有容器的情况下管理内部依赖项。注册点包括工厂方法(通常基于 Func)。然后容器扩展(在单独的程序集中)可以注册并为容器提供工厂方法。用于注册的中间模型也可用于将元数据从容器传输到框架。

标签: c# dependency-injection ioc-container ninject dependency-management


【解决方案1】:

常见的做法是用common service locator抽象容器

MvcExtensions 的作者已经非常成功地抽象出 IoC。

【讨论】:

  • 常用服务定位器接口为只读;您无法使用它注册服务。
【解决方案2】:

两个简单的选项是为您的用户提供一些字典,他们可以在其中注册类型映射,或者只是为他们提供一个容器接口,该接口提供您认为您可能需要的所有服务并允许用户提供自己的包装容器。抱歉,如果这些不太适合您的场景,我主要使用 Unity,所以我不知道 Ninject 等是否会做 Unity 没有做的花哨的事情。

【讨论】:

    【解决方案3】:

    Common Service Locator 是一种方法,但它只包含解析方法,不包含注册方法。

    您可能想看看这是如何在agatha-rrsl project 中实现的。有更完整的解释here,但总之:

    • 为注册和解析类型定义与容器无关的接口
    • 为不同的容器提供实现(或让用户提交实现)

    警告:您可能无法直接在您的库中使用您选择的容器。

    【讨论】:

    • 我以前玩过这个想法;知道其他人正在这样做使它看起来更容易接受:)
    • Aww... 这不是一个好的解决方案。我会使用任何使用服务定位器反模式的 Fx 自动关闭:blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
    • @Mark Seemann 为什么你认为这是这种情况?服务定位器作为一种反模式是对手头问题的正交关注。此外,Agatha 不认可/依赖/推广服务定位器...
    • 您编写了“用于注册和解析类型的容器无关接口”。在我看来,这听起来像是您会根据它们的类型从界面中提取实例。这就是服务位置的定义:blog.ploeh.dk/2010/11/01/…
    • 这个问题/答案根本不是关于服务位置是或否。我们正在讨论如何在可重用库中提供 IoC 设施,而无需将自己绑定到特定的 IoC 容器实现。我真的看不到与 Service Location 的链接(我同意这是一种反模式,但完全不在此处讨论的重点)。
    【解决方案4】:

    我通过使用属性解决了这个问题,并提供了一个扫描器类,用于定位所有实现和它们提供的接口。

    public class ComponentAttribute : Attribute
    {}
    
    // class that should be registered in the container.
    [Component]
    public class MyService : IMyService
    {}
    
    // Contains information for each class that should be 
    // registered in the container.
    public interface IContainerMapping
    {
        public Type ImplementationType {get;}
        public IEnumerable<T> ImplementedServices {get; }
    }
    
    public class ComponentProvider
    {
        public static IEnumerable<IContainerMapping> Find() 
        {
            var componentType = typeof(ComponentAttribute);
            foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
            {
               if (type.GetCustomAttributes(componentType, false).Count == 0)
                  continue;
    
               var mapping = new ContainerMapping(type);
               List<Type> interfaces new List<Type>();
               foreach (var interfac in type.GetInterfaces())
               {
                 //only get our own interfaces.
                 if (interface.Assembly != Assembly.GetExecutingAssembly())
                   continue;
    
                 interfaces.Add(interfac);
               }
    
               mapping.ImplementedServices = interfaces;
               yield return mapping;
            }
        }
    }
    

    此解决方案为用户提供了很大的灵活性。他可以通过直接使用[Component] 属性或使用您的解决方案来提供自己的解决方案。

    用户应该做的事情是这样的:

    foreach (var mapping in ComponentProvider.Find())
        myContainer.Register(mapping.ImplementationType).As(mapping.ImplementedServices);
    

    我通常通过提供一个 MyProject.autofac 项目来创建一个完整的解决方案,该项目将所有内容注册到我最喜欢的容器中。

    【讨论】:

      【解决方案5】:

      Write loosely coupled code。应用程序应该依赖于容器。 Frameworks 不应该。

      【讨论】:

      • 虽然我很欣赏你的观点,但我觉得要求用户在组合根目录中为自己绑定所有内容是不合理的。当然,他们应该定义组合发生的时间,但是在模块内部定义预设绑定不是有利的,然后用户可以使用他们自己的 DI 容器调用这些绑定吗?如果我的框架的 V2 需要额外的子系统服务怎么办?要求用户更新他们的作文根是否公平?
      • 您可以提供合理的默认值。没有理由对用户强加任何东西,而是让可扩展性成为可能。
      • 这让我回到了最初的问题:如何在保持与容器无关的同时定义合理的默认值?绑定是针对容器定义的...
      • 提供 Fx 使用的接口的默认实现。无论如何,这都是很好的 Fx 设计实践。为用户提供 Seams 以覆盖默认行为。
      • 好的,回顾一下(因为我认为我们可能会遇到问题)。我将使用默认实现 ServiceImpl 定义 IService。 在哪里从 IService->ServiceImpl 定义默认绑定?以及如何在不通过容器的情况下定义此绑定,从而保持与容器无关。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-02
      • 2018-09-09
      相关资源
      最近更新 更多