2.3 扩充示例应用程序
为了让我们能够更好地了解DI的重要性,我需要扩展商业应用的样本。到目前为止,我一直把这个应用保持得尽可能简单和小,以便温和地介绍一些核心概念和原则。因为DI的主要目的之一是管理复杂性,我们需要一个复杂的应用来充分体会它的力量。
我将沿着两个轴线扩展应用:架构重构和添加功能。
2.3.1 架构
到目前为止,这个示例应用程序是一个三层的应用程序,但现在我想在用户界面和领域模型之间放置一个演示模型层,如图2.22所示。
我把所有的控制器(Controllers )和视图模型(viewModel)从用户界面层(User Interface Layer)移到呈现模型层(Presentation Model layer),只留下视图(.aspx和.ascx文件)和合成根在用户界面层。
此举的主要原因是为了将合成根与呈现逻辑分开;这样,我可以向你展示不同的配置风格变化,同时尽可能地保持应用程序的不变。
卑微的对象(Humble Object)
不仅仅是出于教学目的,我把应用程序分成了用户界面层和表现模型层;如果我写的所有应用程序都有用户界面,我通常都会这样做。
这种分割提供了表现逻辑(用户界面的行为方式)和渲染(用户界面的外观)之间的明确分离。它把所有的逻辑放在一个可以进行单元测试的层里,把所有的标记放在一个图形设计者可以工作的层里,而不用担心太容易破坏东西。
我们的目标是在用户界面层尽可能少地使用指令性代码,因为我不打算为这一层写任何单元测试。
一个只包含最低限度的代码来启动自己的应用程序根,之后它将所有其他工作委托给可测试的模块,被称为卑微对象。
除了这个架构上的变化,我还想增加一个比我们目前所看到的更丰富的功能。
2.3.2 购物篮功能
特色产品的列表只向我们展示了有限的复杂程度:在只读的情况下,只涉及一个单一的存储库。
合理的下一步是引入购物篮功能。图2.23显示了使用中的购物篮的截图。
为了支持每个用户的购物篮,我需要一个Basket、一个BasketRepository和一系列的支持类。如果你像我一样,你想先看看Basket类:图2.24显示了购物篮和它的物品列表。
从DI的角度来看,Basket和Extent类并不特别有趣:它们都是没有依赖的Plain Old CLR Object(POCO)类。更有趣的是BasketService和支持类,如图2.25所示。
BasketService可以被用来检索用户的购物篮并应用折扣。 它使用抽象的BasketRepository来获取篮子的内容,并使用抽象的BasketDiscountPolicy来应用折扣。 这两个抽象都是通过构造器注入到BasketService中的:
public BasketService(BasketRepository repository,BasketDiscountPolicy discountPolicy)
BasketDiscountPolicy可以是一个简单的实现,它有一个硬编码的策略,比如给首选客户一个5%的折扣,正如我们在本章前面看到的那样。 这个策略由DefaultProductDiscountPolicy实现,而更复杂的、由数据驱动的实现由RepositoryBasketDiscountPolicy提供,它本身使用抽象的DiscountRepository来获取打折产品的列表。这个抽象再次通过构造器注入到RepositoryBasketDiscountPolicy:
public RepositoryBasketDiscountPolicy(DiscountRepository repository)
为了实现这一点,我可以让BasketService来协调对购物篮的操作:添加物品,以及显示和清空购物篮。 要做到这一点,它需要一个BasketRepository和一个BasketDiscountPolicy,(你猜对了)通过其构造函数发布给它:
public BasketService(BasketRepository repository, BasketDiscountPolicy discountPolicy)
为了进一步解决这个问题,我设计了一个名为BasketController的ASP.NET MVC控制器,它围绕着IBasketService接口,我通过它的构造函数将其注入其中:
public BasketController(IBasketService basketService)
如图2.25所示,BasketService类实现了IBasketService,所以我们使用这个实现。
BasketController最终是由一个自定义的IControllerFactory创建的,所以它也需要这些抽象。
如果你在路上迷失了方向,图2.26 显示了一个图,说明了依赖在最终应用中是如何组成的。
自定义的IControllerFactory通过提供各自的依赖来创建BasketController和HomeController的实例。 例如,BasketService使用提供的BasketDiscountPolicy实例来对购物篮应用折扣策略:
var discountedBasket = this.discountPolicy.Apply(b);
它没有意识到在这种情况下,提供的BasketDiscountPolicy是RepositoryBasketDiscountPolicy的一个实例,它本身就是DiscountRepository的一个容器。
这个扩展的示例应用程序是本书其余部分中许多代码示例的基础。
2.4 概要
编写紧密耦合的代码是出乎意料的容易。 甚至当Mary带着明确的意图去写一个三层的应用程序时,它也变成了一个大体上单一的意大利面条代码(当我们谈论分层时,我们称之为千层饼)。
编写紧密耦合的代码之所以如此容易,其中一个原因是语言的特性和我们的工具都已经把我们拉到了这个方向。如果我们需要一个对象的新实例,我们可以使用new关键字,如果我们没有所需程序集的引用,Visual Studio会让我们很容易添加它。
然而,每当我们使用new的关键字时,我们就会引入一个紧耦合。
尽量减少new的使用的最好方法是在我们需要一个依赖的实例时使用构造器注入设计模式。 本章的第二个例子演示了如何通过编程到抽象而不是具体类来重新实现Mary的应用程序。
构造函数注入是一个控制反转的例子,因为我们把对依赖的控制反转了。我们没有用new关键字来创建实例,而是把这个责任委托给了第三方。正如我们将在下一章看到的,我们把这个地方称为组合根。 在这里,我们将所有松耦合的类组成一个应用程序