devise – 在使用温莎时应将物品登记在哪里
我的应用程序中将包含以下组件
- 数据访问
- DataAccess.Test
- 商业
- Business.Test
- 应用
我希望使用温莎城堡作为IoC粘合层,但我有点不确定的胶合devise。
我的问题是谁应该负责注册的对象进入温莎? 我有几个想法;
- 每一层都可以注册自己的对象。 为了testingBL,testing台可以为DAL注册模拟类。
- 每层可以注册其依赖关系的对象,例如,业务层注册数据访问层的组件。 为了testingBL,testing台将不得不卸载“真实”的DAL对象并注册模拟对象。
- 应用程序(或testing应用程序)注册依赖关系的所有对象。
有人能帮助我一些想法和优点/缺点与不同的path? 以这种方式使用温莎城堡的示例项目的链接将是非常有帮助的。
一般而言,应用程序中的所有组件应该尽可能晚地组成,因为这确保了最大程度的模块化,并且模块尽可能松散耦合。
实际上,这意味着你应该在应用程序的根目录下configuration容器。
- 在桌面应用程序中,这将是在主方法(或非常接近它)
- 在ASP.NET(包括MVC)应用程序中,这将在Global.asax中
- 在WCF中,这将在一个ServiceHostFactory
- 等等
容器只是将模块组合到工作应用程序中的引擎。 原则上,您可以手动编写代码(这叫做“ 穷人的DI” ),但使用像Windsor这样的DI容器要容易得多。
这样一个组合根将理想地是应用程序根目录中唯一的代码片断,使得应用程序成为所谓的“ 谦逊可执行文件” (一种优秀的xUnittesting模式 ),它本身不需要unit testing。
你的testing应该不需要容器,因为你的对象和模块应该是可组合的,你可以直接从unit testing中为它们提供testing双精度 。 如果您可以将所有模块devise为与容器无关,那么最好。
在Windsor中也是如此,你应该把你的组件注册逻辑封装在安装程序中(实现IWindsorInstaller
types)请参阅文档了解更多信息
尽pipeMark的答案对于Web场景来说很好,但是将其应用于所有体系结构(即富客户端 – 即:WPF,WinForms,iOS等)的关键缺陷是假设可以/应该创build操作所需的所有组件立刻。
对于Web服务器来说,这是有道理的,因为每个请求都是非常短暂的,并且ASP.NET MVC控制器是由底层框架(无用户代码)为每个请求进入而创build的。因此,控制器及其所有依赖关系可以很容易地编写通过DI框架,这样做的维护成本非常低。 请注意,Web框架负责pipe理控制器的生命周期,并为所有目的pipe理其所有依赖项的生命周期(DI框架将在控制器创build时为您创build/注入)。 在请求的持续时间内依赖关系处于活动状态并且用户代码不需要pipe理组件和子组件本身的生命周期是完全正确的。 另请注意,Web服务器在不同的请求中是无状态的(会话状态除外,但与本次讨论无关),并且您从来没有多个需要同时处理的控制器/子控制器实例来处理单个请求。
但是,在富客户端应用程序中,情况并非如此。 如果使用MVC / MVVM体系结构(您应该!),用户的会话是长期存在的,控制器在用户浏览应用程序时会创build子控制器/同级控制器(请参阅关于底部MVVM的注释)。 与networking世界类比的是,在富客户端应用程序中的每个用户input(button点击,操作执行)都等同于Web框架接收到的请求。 然而,最大的区别在于,您希望富客户端应用程序中的控制器在操作之间保持活跃状态(很可能用户在同一屏幕上执行多个操作(由特定的控制器控制),并且子控制器也可以当用户执行不同的动作(想象一下,如果用户导航到标签控件时懒惰地创build标签,或者如果用户在屏幕上执行特定操作,只需要加载一块UI)就创build和销毁。
这两个特征意味着需要pipe理控制器/子控制器的生命周期的用户代码,并且控制器的依赖性不应该被全部预先创build (即:子控制器,视图模型,其他表示组件等)。 )。 如果您使用DI框架来执行这些职责,那么您不仅会得到更多不属于它的代码(请参阅: 构造函数注入反模式 ),而且还需要传递一个依赖项容器大部分表示层,以便您的组件可以在需要时使用它来创build其子组件。
为什么我的用户代码可以访问DI容器?
1)依赖容器持有对你的应用程序中的很多组件的引用。 将这个坏男孩传递给需要创build/pipe理anoter子组件的每个组件,相当于在你的架构中使用全局variables。 更糟糕的是,任何子组件也可以将新组件注册到容器中,这样也会成为全球存储。 开发人员只会在组件之间传递对象(在兄弟控制器之间或深度控制器层次之间传递数据 – 即:祖先控制器需要从祖父控制器获取数据)。 请注意,在容器不传递给用户代码的networking世界中,这绝不是问题。
2)依赖容器与服务定位器/工厂/直接对象实例化的另一个问题是,从容器中parsing使得它完全不明确,无论是创build组件还是简单地重用现有组件。 相反,它是由一个集中configuration(即:bootstrapper /组合根)来弄清楚组件的生命周期。 在某些情况下,这是可以的(即:Web控制器,它不是需要pipe理组件生命周期的用户代码,而是运行时请求处理框架本身)。 这是非常成问题的,但是当你的组件的devise应该指出是否他们有责任pipe理一个组件和它的生命周期应该是什么(例如:一个电话应用程序popup一个表单,询问用户的一些信息,这是通过控制器创build一个控制覆盖表单的子控制器,一旦用户input了一些信息,表单就会被放弃,并且控制权返回给初始控制器,控制器仍然保持着用户以前所做的状态)。 如果使用DI来parsing工作表子控制器,则它应该是什么时间或应该由谁来负责pipe理它(启动控制器)是不明确的。 将其与使用其他机制所规定的明确责任进行比较。
情景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不清楚是谁负责子组件的生命周期pipe理。
注意:从技术上讲,许多DI框架确实有一些懒惰地创build组件的方法(请参阅: 如何不执行dependency injection – 静态或单例容器 ),这比传递容器好多了,但是仍然需要支付改变你的代码以遍布各处的创build函数,在创build过程中缺less对有效的构造函数参数的一级支持,并且在一天结束的时候,你仍然在不必要的地方使用间接机制,只能获得可testing性,这可以以更好,更简单的方式实现(见下文)。
这是什么意思?
这意味着DI适用于某些场景,不适合他人。 在富客户端应用程序中,它恰好带来了很多缺点,只有很less的优势。 您的应用越复杂,维护成本越高。 它也带有严重的滥用潜力,这取决于您的团队沟通和代码审查stream程的严密程度,可以是从非发行到严重的技术债务成本的任何地方。 有一个神话,服务定位器或工厂或旧的Instantiation是一些坏的和过时的机制,只是因为它们可能不是在web应用程序世界的最佳机制,也许有很多人玩。我们不应该过度使用,把这些学习推广到所有场景,并把所有东西都看成是钉子,因为我们已经学会了使用特定的锤子。
我对“富客户端应用程序”的build议是使用最小的机制来满足每个组件的需求。 80%的时间,这应该是直接的瞬间。 服务定位器可以用来容纳你的主要业务层组件(即:本质上是单一的应用服务),当然工厂甚至Singleton模式也有它们的位置。 没有什么可说的,你不能使用隐藏在你的服务定位器后面的DI框架去创build你的业务层依赖关系和他们依赖的所有东西 – 如果这样做最终会让你的生活变得更容易,展现了富客户端performance层压倒性地做的懒惰加载 。 只要确保屏蔽您的用户代码访问该容器,以防止通过DI容器的混乱可以创build。
可testing性呢?
无需DI框架就可以完成可testing性。 我build议使用拦截框架,如UnitBox (免费)或TypeMock (昂贵)。 这些框架为您提供了解决手头问题所需的工具(您如何模拟C#中的实例化和静态调用),并且不要求您更改整个体系结构以避开它们(不幸的是,趋势所在的位置去了.NET / Java世界)。 find解决手头问题的方法是明智的,并使用最佳的自然语言机制和模式,然后尝试将每个方块插入圆形DI孔。 一旦你开始使用这些更简单,更具体的机制,你会注意到你的代码库中几乎不需要DI。
注意:对于MVVM体系结构
在基本的MVVM体系结构中,视图模型有效地承担了控制器的责任,因此出于所有目的,将上面的“控制器”措辞应用于“视图模型”。 基本的MVVM对小应用程序工作正常,但随着应用程序复杂性的增长,您可能需要使用MVCVM方法。 视图模型变成大多数愚笨的DTO,以促进与视图的数据绑定,同时与业务层的交互以及代表屏幕/子屏幕的视图模型组之间被封装成明确的控制器/子控制器组件。 在任何一种架构中,控制器的责任都存在,并展现出上述相同的特征。