Ioc/DI - 为什么我必须在应用程序的入口点引用所有层/组件?

die*_*ohb 114 dependency-injection castle-windsor inversion-of-control

(与此问题相关,EF4:为什么在启用延迟加载时必须启用代理创建?).

我是DI的新手,所以请耐心等待.我知道容器负责实例化我所有已注册的类型,但为了做到这一点,它需要引用我的解决方案中的所有DLL及其引用.

如果我没有使用DI容器,我就不必在我的MVC3应用程序中引用EntityFramework库,只需引用我的业务层,它将引用我的DAL/Repo层.

我知道在一天结束时所有的DLL都包含在bin文件夹中,但我的问题是必须通过VS中的"添加引用"显式引用它,以便能够发布包含所有必需文件的WAP.

Mar*_*ann 186

如果我没有使用DI容器,我不必在我的MVC3应用程序中引用EntityFramework库,只需要引用我的DAL/Repo层的业务层.

是的,这正是DI很难避免的情况:)

使用紧密耦合的代码,每个库可能只有一些引用,但这些引用还有其他引用,创建了依赖关系的深层图,如下所示:

深图

因为依赖图是深的,这意味着大多数库沿着很多其他依赖的拖动-例如,在图中,库C沿拖动库H,图书馆E,图书馆Ĵ,图书馆男,图书馆ķ库Ñ.这使得重新使用每个库变得更加困难 - 例如在单元测试中.

但是,在松散耦合的应用程序中,通过将所有引用移动到组合根,依赖关系图将严重展平:

浅图

如绿色所示,现在可以重用库C而不拖动任何不需要的依赖项.

然而,所有的说,很多DI容器,你不要硬引用添加到所有需要的库.相反,您可以使用基于约定的程序集扫描(首选)或XML配置的形式进行后期绑定.

但是,当您这样做时,必须记住将程序集复制到应用程序的bin文件夹,因为它不再自动发生.就个人而言,我很少发现值得付出额外的努力.

这个答案的更精细版本可以在我的书依赖注入,原理,实践,模式的摘录中找到.

  • @AndyDangerGagne组合根是DI模式 - [服务定位器的对面](http://www.infoq.com/articles/Succeeding-Dependency-Injection).从组合根的角度来看,没有一种类型是多态的; 组合根将所有类型视为具体类型,因此,Liskov替换原则不适用于它. (7认同)
  • 这个答案适用于.NET之外.您可能需要参考Robert C. Martin的*包装设计原则*章节,例如[敏捷软件开发,原理,模式和实践](http://amzn.to/195wrPB) (5认同)
  • 非常感谢,这现在非常有意义..我需要知道这是否是设计的.至于强制正确使用依赖关系,我已经实现了一个单独的项目,我的DI引导程序就像下面提到的Steven,其中我引用了其余的库.此项目由入口点应用程序引用,在完整构建结束时,这会导致所有必需的dll位于bin文件夹中.谢谢! (3认同)
  • 作为一般规则,接口应由客户端使用它们定义([APP,ch.11](http://amzn.to/19W4JHk)),因此如果库J需要接口,则应在库J中定义这是依赖倒置原则的必然结果. (3认同)
  • @Mark Seemann这个问题/答案是否针对微软?我想知道将所有依赖项移动到"应用程序的入口点"的想法是否对使用Maven的Java EE/Spring项目有意义...谢谢! (2认同)
  • 我花了很长时间才找到这个问题和答案.虽然有很多关于组合根是什么的阅读,但我觉得描述在实际应用程序中或在单独的程序集中具有组合根的优缺点的文章较少.我不清楚手动复制程序集(使用后期绑定时)是"要走的路",但这个答案澄清了这一点! (2认同)
  • @MarkSeemann 使用 CompositionRoot 方法时,您如何处理库之间的引用?例如库 J 需要和库 N 中的接口实现。你会把这个接口放在哪个包中?你会制作一个像“通用接口”这样的专用包,所有库和组合根都会引用它吗?或者您甚至可以为每个库制作一个“接口包”(假设这些库实现了不同的功能)? (2认同)
  • @MichałJankowski如果您已经引用了所有库,那么您还应该引用定义接口的库.在任何情况下,根据SOLID,接口由客户端拥有,因此通常应与使用它们的客户端(在同一个库中)一起定义.阅读[Agile PPP](http://amzn.to/19W4JHk). (2认同)
  • @MarkSeemann读到这个让我觉得插图有点误导(其中一些评论也表明).插图中的箭头称为库之间的引用,在.NET世界中,它们很容易被解释为Visual Studio中项目之间的引用.如果是这种情况,那么第二个例子就是简单的说明.鉴于库C更易于重用的示例 (2认同)
  • <继续>但是图书馆H怎么样?从图中可以看出它也很容易重用,但是如图所示,库C应该定义它需要的接口,所以H现在有一个对C的引用,以便实现它的接口,以便它可以被注入C.这不是在第二个插图中显示.另一方面,如果插图中的箭头表示"谁是新人"那么它更有意义,但附图中的文字表示第一个故事. (2认同)
  • @Terje 插图是为了说明这一点。如图所示,您不必完全展平依赖关系层次结构。在第二个图中,只有一个依赖层。实际上,由于您概述的原因,您可能需要两层。关键是你应该偏爱平面依赖图而不是深度依赖图。 (2认同)
  • @Terje BTW,你的问题假设接口*必须*在其他自定义库中定义,而*不具备*.例如,在.NET中,我经常使用`IEnumerable <T>`或`IObserver <T>`作为依赖项.这些在BCL中定义,因此所有消费者和实施者都可以自动使用. (2认同)

Ste*_*ven 63

如果我没有使用DI容器,我就不必在我的MVC3应用程序中引用EntityFramework库

即使使用DI容器,您也不必让MVC3项目引用EF,但您(隐式)选择通过在MVC3项目中实现组合根(组成对象图的启动路径)来实现此目的.如果您非常严格地使用程序集来保护架构边界,则可以将Composition Root或您的(MVC)演示文稿移动到类库中.

在第一个选项中,您让MVC3项目引用这个单独的"引导程序"程序集,它将引用解决方案中的所有其他程序集,并引用您的DI容器库.这个问题是这个bootstrapper项目不能引用位于MVC3项目中的类型(因为它会导致循环程序集依赖).这些类型必须移动到引导程序项目(可能需要引用System.Web.Mvc),或者您需要在MVC3应用程序中保留容器配置的一小部分.另请注意,您的MVC项目仍然通过新的引导程序集程序间接引用所有其他程序集,因为程序集依赖项是可传递的.

虽然将Composition Root放在一个单独的程序集中是有效的,但是当有多个终端应用程序(即web应用程序+ Web服务+ Windows服务)时,大多数DI purist(包括我)通常只将组合根移动到类库. )使用相同的业务层.当我有一个应用程序时,我将Composition Root保留在我的最终应用程序中.

第二个选项是将所有与MVC相关的类(视图,控制器等)从启动项目移动到类库.这允许这个新的表示层组件与应用程序的其余部分保持断开连接.您的Web应用程序项目本身将成为一个非常薄的shell,具有所需的启动逻辑.Web应用程序项目将是引用所有其他程序集的组合根.

在使用MVC时,将表示逻辑提取到类库会使事情变得复杂.由于控制器和视图,图像,css文件等不在启动项目中,因此将所有内容连接起来将更加困难.这可能是可行的,但需要更多时间来设置.

这两个选项都有它们的缺点,这就是为什么我通常建议将组合根保留在Web项目中.许多开发人员不希望他们的MVC程序集依赖于DAL程序集,但这不是一个真正的问题.不要忘记程序集是部署工件; 您将代码拆分为多个程序集,以允许单独部署代码.另一方面,架构层是逻辑工件.在同一个程序集中拥有多个层是非常可能的(也是常见的).

在这种情况下,我们最终会在同一个Web应用程序项目中具有组合根(层)和表示层(因此在同一个程序集中).即使该程序集引用包含DAL的程序集,表示仍然不引用数据访问.这是一个很大的区别.

当然,当我们这样做时,我们失去了编译器在编译时检查这个架构规则的能力,但这应该不是问题.大多数架构规则实际上都不能由编译器检查,并且总是存在类似常识的东西.如果您的团队中没有常识,您可以随时使用代码审查(每个团队都应该IMO总是这样做).您还可以使用NDepend(商业版)等工具,它可以帮助您验证架构规则.当您将NDepend与构建过程集成时,它可以在有人检查违反此类架构规则的代码时发出警告.

您可以在我的书依赖注入,原理,实践,模式的第4章中阅读关于组合根如何工作的更详细的讨论.


Roo*_*ian 5

如果我不使用DI容器,则不必在MVC3应用程序中引用EntityFramework库,而只需引用业务层即可引用DAL / Repo层。

您可以创建一个名为“ DependencyResolver”的单独项目。在此项目中,您必须引用所有库。

现在,UI层不需要NHibernate / EF或任何其他与UI不相关的库,除了Castle Windsor可以被引用。

如果要在UI层中隐藏Castle Windsor和DependencyResolver,则可以编写一个HttpModule来调用IoC注册表内容。

我只有一个StructureMap的例子:

public class DependencyRegistrarModule : IHttpModule
{
    private static bool _dependenciesRegistered;
    private static readonly object Lock = new object();

    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, args) => EnsureDependenciesRegistered();
    }

    public void Dispose() { }

    private static void EnsureDependenciesRegistered()
    {
        if (!_dependenciesRegistered)
        {
            lock (Lock)
            {
                if (!_dependenciesRegistered)
                {
                    ObjectFactory.ResetDefaults();

                    // Register all you dependencies here
                    ObjectFactory.Initialize(x => x.AddRegistry(new DependencyRegistry()));

                    new InitiailizeDefaultFactories().Configure();
                    _dependenciesRegistered = true;
                }
            }
        }
    }
}

public class InitiailizeDefaultFactories
{
    public void Configure()
    {
        StructureMapControllerFactory.GetController = type => ObjectFactory.GetInstance(type);
          ...
    }
 }
Run Code Online (Sandbox Code Playgroud)

DefaultControllerFactory并不直接使用IoC容器,而是将其委托给IoC容器方法。

public class StructureMapControllerFactory : DefaultControllerFactory
{
    public static Func<Type, object> GetController = type =>
    {
        throw new  InvalidOperationException("The dependency callback for the StructureMapControllerFactory is not configured!");
    };

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(requestContext, controllerType);
        }
        return GetController(controllerType) as Controller;
    }
}
Run Code Online (Sandbox Code Playgroud)

GetController代表是在StructureMap登记处(在Windsor它应该是一个安装程序)设置。

  • 一个小小的好处是没有人可以在 UI 中使用 IoC 容器。即没有人能够使用 IoC 容器作为 UI 中的服务定位器。 (2认同)
  • 此外,它禁止开发人员在 UI 层中意外使用 DAL 代码,因为在 UI 中没有对程序集的硬引用。 (2认同)