代码之家  ›  专栏  ›  技术社区  ›  Fredrik Jansson

设计-使用温莎[关闭]时,应在何处登记对象

  •  46
  • Fredrik Jansson  · 技术社区  · 16 年前

    我将在应用程序中包含以下组件

    • 数据访问
    • 数据访问测试
    • 业务
    • 业务测试
    • 应用

    我本来想用温莎城堡作为国际奥委会的胶水层在一起,但我有点不确定的设计胶。

    我的问题是谁应该负责把这些物品登记到温莎? 我有几个想法;

    1. 每个层都可以注册自己的对象。为了测试BL,测试台可以为DAL注册模拟类。
    2. 每个层都可以注册其依赖项的对象,例如,业务层注册数据访问层的组件。为了测试BL,测试台必须卸载“真实”DAL对象并注册模拟对象。
    3. 应用程序(或测试应用程序)注册依赖项的所有对象。

    有人能用不同的方法帮我解决一些想法和优缺点吗?链接到以这种方式利用温莎城堡的示例项目将非常有用。

    2 回复  |  直到 10 年前
        1
  •  73
  •   Krzysztof Kozmic    10 年前

    一般来说,应用程序中的所有组件都应该尽可能晚地组成,因为这样可以确保最大的模块性,并且模块尽可能松散地耦合。

    实际上,这意味着您应该在应用程序的根目录下配置容器。

    • 在桌面应用程序中,这是主要的方法(或非常接近它)。
    • 在ASP.NET(包括MVC)应用程序中,它将位于global.asax中
    • 在WCF中,它将位于ServiceHostfactory中
    • 等。

    容器只是将模块组合到工作应用程序中的引擎。原则上,您可以手工编写代码(这称为 穷人的迪 但是使用像温莎这样的DI容器要容易得多。

    这样的 组成根 理想情况下,它将是应用程序根目录中的唯一一段代码,使应用程序成为所谓的 不起眼的可执行文件 (从优秀的 xUnit Test Patterns )这本身不需要单元测试。

    您的测试根本不需要容器,因为您的对象和模块应该是可组合的,并且您可以直接提供 双倍测试 从单元测试中得到。最好能将所有模块设计成不区分容器的模块。

    另外,在windsor中,您应该将组件注册逻辑封装在安装程序中(类型实现 IWindsorInstaller )参见 the documentation 有关详细信息

        2
  •  25
  •   Marchy    13 年前

    虽然Mark的答案对于Web场景来说很好,但是将其应用于所有体系结构(即富客户机,即WPF、WinForms、iOS等)的关键缺陷在于假设可以/应该同时创建操作所需的所有组件。

    对于Web服务器来说,这是有意义的,因为每个请求都是非常短暂的,并且底层框架(没有用户代码)会为每个传入的请求创建一个ASP.NET MVC控制器。因此,控制器及其所有依赖项可以很容易地由一个DI框架组成,并且这样做的维护成本很低。请注意,Web框架负责管理控制器的生存期,并为所有目的管理其所有依赖项的生存期(DI框架将在控制器创建时为您创建/注入)。依赖项在请求的持续时间内存在是完全可以的,并且您的用户代码不需要管理组件和子组件本身的生存期。另外请注意,Web服务器在不同的请求中是无状态的(会话状态除外,但这与本讨论无关),并且您永远不会有多个控制器/子控制器实例需要同时存在以服务单个请求。

    然而,在富客户端应用程序中,情况并非如此。 如果使用MVC/MVVM体系结构(您应该这样做!)用户的会话是长期存在的,当用户浏览应用程序时,控制器会创建子控制器/同级控制器(请参见底部有关MVVM的说明)。与Web世界类似的是,富客户机应用程序中的每个用户输入(单击按钮,执行操作)都相当于Web框架正在接收的请求。但是,最大的区别在于,您希望富客户端应用程序中的控制器在操作之间保持活动状态(很可能用户在同一屏幕上执行多个操作-这由特定控制器控制),并且在用户执行不同操作时创建和销毁子控制器(考虑选项卡控件,该控件在用户导航到选项卡时惰性地创建选项卡,或者仅当用户在屏幕上执行特定操作时才需要加载的一块UI)。

    这两个特征都意味着 需要管理控制器/子控制器的生存期的是用户代码,并且不应预先创建控制器的所有依赖项。 (即:子控制器、视图模型、其他表示组件等)。如果您使用一个DI框架来执行这些职责,那么您将得到的不仅仅是不属于它的更多代码(请参见: Constructor over-injection anti-pattern )但是,您还需要在整个表示层中传递依赖容器,以便您的组件可以在需要时使用它来创建子组件。

    为什么我的用户代码可以访问DI容器很糟糕?

    1)依赖容器保存对应用程序中许多组件的引用。将这个坏小子传递给每个需要创建/管理其他子组件的组件,相当于在体系结构中使用全局。更糟糕的是,任何子组件也可以将新组件注册到容器中,以便它尽快成为全局存储。开发人员将把对象扔到容器中,只是为了在组件之间传递数据(在兄弟控制器之间或在深度控制器层次结构之间,即祖先控制器需要从祖辈控制器获取数据)。 请注意,在没有将容器传递给用户代码的Web世界中,这从来都不是问题。

    2)依赖容器与服务定位器/工厂/直接对象实例化的另一个问题是,从容器解析会使创建组件或简单地重用现有组件完全不明确。相反,它由一个集中配置(即引导程序/组合根目录)来确定组件的生存期。在某些情况下,这是可以的(即:Web控制器,其中不需要用户代码来管理组件的生存期,而是运行时请求处理框架本身)。然而,当您的组件的设计应该表明管理组件是否是他们的责任,以及它的生命周期应该是什么时,这是一个非常有问题的问题(例如:一个电话应用程序弹出一张表,要求用户提供一些信息)。这是通过控制器创建一个子控制器来实现的,该子控制器控制覆盖表。一旦用户输入了一些信息,工作表就被退出,控制权返回给初始控制器,初始控制器仍然保持用户之前所做的状态)。如果使用DI来解决Sheet子控制器,那么它的生存期应该是什么,或者应该由谁负责管理它(初始控制器)。将其与使用其他机制所规定的明确责任进行比较。

    场景A:

    // not sure whether I'm responsible for creating the thing or not
    DependencyContainer.GimmeA<Thing>()
    

    方案B:

    // responsibility is clear that this component is responsible for creation
    
    Factory.CreateMeA<Thing>()
    // or simply
    new Thing()
    

    方案C:

    // responsibility is clear that this component is not responsible for creation, but rather only consumption
    
    ServiceLocator.GetMeTheExisting<Thing>()
    // or simply
    ServiceLocator.Thing
    

    从技术上讲,许多DI框架确实有一些创建方法 组件延迟(请参见: How not to do dependency injection - the static or singleton container )哪个比传球好多了 容器在周围,但是你仍然要为改变你的 到处传递创建函数的代码,缺少第一级 支持在创建过程中传入有效的构造函数参数, 一天结束时,你仍在使用间接机制 在只有实现可测试性才有好处的地方不必要, 这可以通过更好、更简单的方式实现(见下文)。

    这一切意味着什么?

    这意味着DI适用于某些场景,而不适用于其他场景。在富客户机应用程序中,DI有很多缺点,而优点很少。你的应用程序在复杂性上扩展得越远,维护成本就越高。它还具有严重的误用可能性,这取决于您的团队沟通和代码审查过程的紧密程度,可能是任何地方,从没有问题到严重的技术债务成本。围绕着服务定位器或工厂或旧的实例,有一个神话在某种程度上是坏的和过时的机制,仅仅是因为它们可能不是Web应用程序世界中的最佳机制,在这个世界中可能有很多人参与其中。我们不应该过于笼统地将这些知识推广到所有的场景中,仅仅因为我们已经学会了使用一把特殊的锤子,就把所有的事情都看成是钉子。

    我的建议 对于富客户端应用程序 是使用满足手头每个组件要求的最小机制。80%的时间应该是直接瞬间启动。服务定位器可以用来存放您的主要业务层组件(即:应用程序服务,在本质上通常是单例的),当然工厂甚至单例模式也有它们的位置。 没有什么可以说,您不能使用隐藏在服务定位器后面的DI框架来创建您的业务层依赖性,以及它们在一次行动中所依赖的一切——如果这最终使您在该层中的生活更容易,并且该层没有表现出富客户端表示层绝大多数所做的懒惰加载。 . 只要确保保护您的用户代码不被访问那个容器,这样您就可以避免传递DI容器所造成的混乱。

    可测试性呢?

    无需DI框架就可以完全实现可测试性。 我建议使用拦截框架,例如 UnitBox (免费)或 TypeMock (昂贵)这些框架为您提供了解决手头问题的工具(如何在C++语言中模拟实例化和静态调用),不需要改变整个体系结构来绕过它们(不幸的是,在.NET/Java世界中趋势已经消失)。更明智的做法是找到解决问题的方法,并使用最适合底层组件的自然语言机制和模式,然后尝试将每一个方栓放入圆形DI孔中。一旦您开始使用这些更简单、更具体的机制,您将注意到代码库中几乎不需要DI(如果有的话)。

    注:对于MVVM架构

    在基本的MVVM架构中,视图模型有效地 管制员的责任,因此无论出于何种目的考虑 上面的“控制器”措辞适用于“视图模型”。基本MVVM工程 对于小型应用程序来说很好,但是随着应用程序复杂性的增加,您可能需要 使用MVCVM方法。视图模型变得非常愚蠢 在与 业务层和视图模型组之间表示 屏幕/子屏幕被封装到显式 控制器/子控制器组件。在任何一种架构中, 管制员的责任存在并表现出同样的情况 以上讨论的特性。