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


123

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

我是DI的新手,所以请多多包涵。我知道容器负责实例化我所有的注册类型,但要这样做,它需要引用我的解决方案中的所有DLL及其引用。

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

我知道最终所有DLL都包含在bin文件夹中,但是我的问题是必须能够通过VS中的“添加引用”显式引用它,以便能够发布包含所有必要文件的WAP。


1
该摘录摘自.NET依赖注入》第二版,这是Mark和我自己的答案的更详尽的版本。它详细描述了“ 复合根”的概念,以及为什么让应用程序的启动路径依赖于每个其他模块实际上是一件好事。
史蒂文

我通读了摘录链接和第1章,因为我真的很喜欢对DI的复杂问题进行类比和简单的解释,所以我将购买这本书。我认为您应该提出一个新的答案,明确回答“除非您也是合成的根,否则您不必引用条目逻辑层中的所有层/程序集”,链接至摘录,然后将图像贴到图3中。摘抄。
diegohb

Answers:


194

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

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

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

深图

因为依赖关系图很深,所以意味着大多数库都沿着许多其他依赖关系拖动-例如,在图中,库C沿着库H,库E,库J,库M,库K库N拖动。这使得很难独立于其他库来重用每个库,例如在单元测试中

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

浅图

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

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

但是,执行此操作时,必须记住将程序集复制到应用程序的bin文件夹中,因为该操作不再自动发生。就个人而言,我很少觉得值得付出额外的努力。

可以从我的书《依赖注入,原理,实践,模式》的摘录中找到该答案的更详尽的版本。


3
非常感谢,这现在很有意义了。我需要知道这是否是设计使然。至于正确使用依赖项,我已经用我的DI引导程序实现了一个单独的项目,例如下面提到的Steven,其中引用了其余的库。入口点应用程序引用了该项目,在完整构建的结尾,这导致所有必需的dll位于bin文件夹中。谢谢!
diegohb 2012年

2
@Mark Seemann这个问题/答案特定于Microsoft吗?我想知道将所有依赖关系移到“应用程序的入口”的想法对使用Maven的Java EE / Spring项目是否有意义……谢谢!
格雷ç

5
这个答案不仅仅适用于.NET。您可能要参考例如敏捷软件开发,原理,模式和实践中的 Robert C. Martin的“包装设计原理”一章
Mark Seemann,2013年

7
@AndyDangerGagne合成根是一种DI模式- 与Service Locator相反。从成分根的角度来看,类型都不是多态的。组合根将所有类型都视为具体类型,因此,李斯科夫替代原理不适用于它。
Mark Seemann 2014年

4
通常,接口应由使用它们的客户端定义(APP,第11章),因此,如果库J需要接口,则应在库J中定义接口。这是依赖关系反转原理的必然结果。
Mark Seemann 2014年

65

如果我不使用DI容器,则不必在MVC3应用程序中引用EntityFramework库。

即使使用DI容器,也不必让MVC3项目引用EF,但您(隐式)选择通过在MVC3项目中实现Composition Root(组成对象图的启动路径)来选择这样做。如果对使用程序集保护体系结构边界非常严格,则可以将表示逻辑移至其他项目。

当您将所有与MVC相关的逻辑(控制器等)从启动项目移到类库时,它允许此表示层程序集与应用程序的其余部分保持断开连接。您的Web应用程序项目本身将成为具有所需启动逻辑的非常薄的外壳。该Web应用程序项目将是引用所有其他程序集的“合成根”。

使用MVC时,将表示逻辑提取到类库会使事情复杂化。由于控制器不在启动项目中(因此视图,图像,css文件可能必须留在启动项目中),因此很难将所有内容连接起来。这可能是可行的,但将花费更多的时间来设置。

由于不利因素,我通常建议仅将合成根保留在Web项目中。许多开发人员不希望其MVC程序集依赖于DAL程序集,但这并不是真正的问题。不要忘记程序集是一个部署工件。您将代码拆分为多个程序集,以允许分别部署代码。另一方面,体系结构层是逻辑工件。在同一装配体中很可能(而且很常见)有多个层。

在这种情况下,我们最终将在同一Web应用程序项目(因此在同一程序集中)中拥有“合成根”(图层)和“表示层”。并且即使该程序集引用了包含DAL的程序集,表示仍未引用数据访问。这是一个很大的区别。

当然,当我们这样做时,我们将失去编译器在编译时检查该体系结构规则的能力,但这不是问题。实际上,大多数体系结构规则都不能由编译器检查,总有一些常识。而且,如果您的团队没有常识,您可以随时使用代码审查(IMO应该每个团队都这样做)。您还可以使用NDepend之类的工具(商用),该工具可帮助您验证体系结构规则。当您将NDepend与构建过程集成在一起时,当有人检查其中的代码违反了这种体系结构规则时,它会发出警告。

您可以在我的《依赖注入,原理,实践,模式》一书的第4章中阅读有关“构图根”工作原理的更详尽的讨论。


我的解决方案是一个用于引导的单独项目,因为我们没有ndepend,而且我以前从未使用过。我将对此进行研究,因为当只有一个终端应用程序时,这听起来是一种更好的方法来完成我要尝试的工作。
diegohb 2012年

1
最后一段是很棒的一段,它开始帮助我改变对在单独的组件中保留图层的严格程度的看法。如果您在编写代码的过程中采用其他过程(例如代码检查)来确保UI代码中没有DAL类的引用,则在一个程序集中具有两个或多个逻辑层实际上很好。
BenM 2015年

6

如果我不使用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);
          ...
    }
 }

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;
    }
}

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


1
我喜欢这个比我最终做的更好,模块很棒。那么我该在哪里调用Container.Dispose()?模块内的ApplicationEnd或EndRequest事件...?
diegohb 2012年

1
@Steven因为Global.asax在您的MVC UI层中。HttpModule将在DependencyResolver项目中。
Rookian 2012年

1
小好处是没有人可以在UI中使用IoC容器。也就是说,没有人能够将IoC容器用作UI中的服务定位器。
Rookian 2012年

1
而且,由于在UI中没有硬性引用程序集,因此它也使开发人员不会在UI层中意外使用DAL代码。
diegohb 2012年

1
我已经弄清楚了如何使用Bootstrapper的通用注册API进行相同的操作。我的UI项目引用了Bootstrapper(依赖关系解决方案项目,我在其中绑定了注册),并在Core(用于接口)中进行了项目,但没有其他项目,甚至没有我的DI Framework(SimpleInjector)。我正在使用OutputTo nuget将dll复制到bin文件夹。
diegohb

0
  • 有一个依赖关系:如果一个对象实例化另一个对象。
  • 没有依赖关系:如果对象期望抽象(构造函数注入,方法注入...)
  • 程序集引用(引用dll,webservices ..)与依赖项概念无关,因为要解析抽象并能够编译代码,该层必须引用它。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.