- 高层模块不该该依赖于底层模块,两者都应该依赖于抽象。
- 抽象不该该依赖于细节,细节应该依赖于抽象。
该原则理解起来稍微有点抽象,咱们能够将该原则通俗的理解为:"依赖于抽象”。html
该规则告诉咱们,程序中全部的依赖关系都应该终止于抽象类或者接口
,从而达到松耦合的目的。由于咱们在应用程序中编写的大多数具体类都是不稳定的
。咱们不想直接依赖于这些不稳定的具体类。经过把它们隐藏在抽象和接口
的后面,能够隔离它们的不稳定性。java
一个Button对象会触发Click方法,当被按下时,会调用Light对象的TurnOn方法,不然会调用Light对象的TurnOff方法。git
这个设计存在两个问题:github
这个方案对那些须要被Button控制的对象提出了一个约束。须要被Button控制的对象必需要实现ISwitchableDevice接口。web
所为原则
,只是描述了什么是对的,可是并无说清楚如何去作。在软件工程中,咱们常用DI(依赖注入)
来达到这个目的。可是提到依赖注入,人们又会常常提起IoC
这个术语。因此先让咱们来了解下什么是IoC。api
IoC的全名是Inverse of Control,即控制反转。这一术语并非用来描述面向对象的某种原则或者模式,IoC体现为一种流程控制的反转,通常用来对框架进行设计。mvc
ReportService是一个用来显示报表的流程,该流程包括Trim()
,Clean()
,Show()
三个环节。app
public class ReportService { private string _data; public ReportService(string data) { _data = data; } public void Trim(string data) { _data = data.Trim(); } public void Clean() { _data = _data.Replace("@", ""); _data = _data.Replace("-", ""); //...other rules } public void Show() { Console.WriteLine(_data); } }
客户端经过下面的方式使用该服务:框架
var reportService = new ReportService(input); reportService.Trim(input); reportService.Clean(); reportService.Show();
这样的一个设计体现了过程式的思考方式,客户端依次调用每一个环节从而组成了整个报表显示流程,这样的代码体现了:客户端拥有流程控制权。asp.net
咱们来分析下这段代码,ReportService提供了3个可重用的Api,正如ReportService的命名同样,它告诉咱们它是一个服务,咱们只能重用他提供的三个服务,它没法提供一个打印报表的流程,整个流程是客户端来控制的。
另外,该设计也违反了tell, Don't ask原则。
打印报表做为一个可复用的流程,不但能够提供可复用的流程环节,还能够提供可复用的流程的定义,当咱们进行框架设计的时候,每每会将整个流程控制定制在框架之中,而后提供扩展点供客户端定制。这样的思想体现了流程的全部权从客户端到框架的反转。
好比asp.net mvc或者asp.net api框架,内部定义了http消息从请求,model binder,controller的激活,action的执行,返回response
等可复用的流程。同时还提供了每个环节的可扩展点。
利用以上思想,咱们对ReportService从新设计。
采用IoC思想从新设计该报表服务,将原来客户端拥有的流程控制权反转在报表服务框架中。ReportService
这样的命名已经不适合咱们的想法,新的实现不但提供了报表打印的相关服务,同时还提供了一个可复用的流程,所以从新命名为ReportEngine
。咱们能够经过模板方法达到此目的:
public class ReportEngine { private string _data; public ReportEngine(string data) { _data = data; } public void Show() { Trim(); Clean(); Display(); } public virtual void Trim() { _data = _data.Trim(); } public virtual void Clean() { _data = _data.Replace("@", ""); _data = _data.Replace("-", ""); } public virtual void Display() { Console.WriteLine(_data); } }
此时的报表服务在Show()
方法中定义好了一组可复用的流程,客户端只须要根据本身的需求重写每一个环节便可。客户端能够经过下面的方式使用ReportEngine
var reportEngine=new StringReportEngine(input); reportEngine.Show();
DI即依赖注入,主要解决了2个问题:
除此以外,使用依赖注入还能够带来如下好处:
我记得以前在stackoverflow上看到过相似这样的一个问题:
如何给5岁小孩解释什么叫DI?
得分最高的答案是:小孩在饿的时候只需喊一声我要吃饭便可,而无需关注吃什么,饭是怎么来的等问题。
public class Kid { private readonly IFoodSupplier _foodSupplier; public Kid(IFoodSupplier foodSupplier) { _foodSupplier = foodSupplier; } public void HaveAMeal() { var food = _foodSupplier.GetFood(); //eat } }
DI的背后是一个DI Container(DI容器)在发挥做用。DI之因此可以工做须要两个步骤:
组件注册到DI容器中有3种方式:
.net平台中的大多数DI框架都经过第三种方式进行组件注册,为了介绍这3种不一样的注册方式,咱们经过Java平台下的Spring框架简单介绍:Java中的Spring最先以XML文件的方式进行组件注册,发展到目前主要经过Annotation来注册。
假如咱们有CustomerRepository
接口和相应的实现CustomerRepositoryImpl
,下面用三种不一样的方式将CustomerRepository
和CustomerRepositoryImpl
的对应关系注册在DI容器中:
public interface CustomerRepository { List<Customer> findAll(); } public class CustomerRepositoryImpl implements CustomerRepository { public List<Customer> findAll() { List<Customer> customers = new ArrayList<Customer>(); Customer customer = new Customer("Bryan","Hansen"); customers.add(customer); return customers; } }
<bean name="customerRepository" class="com.thoughtworks.xml.repository.CustomerRepositoryImpl"/>
@Repository("customerRepository") public class CustomerRepositoryImpl implements CustomerRepository { public List<Customer> findAll() { //... } }
@Configuration public class AppConfig { @Bean(name = "customerRepository") public CustomerRepository getCustomerRepository() { return new CustomerRepositoryImpl(); } }
appContext.getBean("customerService", CustomerService.class);
一旦咱们将全部组件都注册在容器中,就能够靠DI容器进行依赖注入了。
正如上面Kid
的实现同样,咱们经过构造器来注入IFoodSupplier
组件,这种方式也是依赖注入最佳方式。
public class Kid2 { public IFoodSupplier FoodSupplier { get; set; } public void HaveAMeal() { var food = FoodSupplier.GetFood(); //eat } }
即经过一个可读写的属性完成注入,该方案的缺点在于为了达到依赖注入的目的而破坏了对象的封装性,因此不推荐。
经过添加方法的参数来完成注入,通常来讲这种方式均可以经过构造器注入的方式来替换,因此也不太经常使用。值得一提的是asp.net core源码中用到了这种注入方式。
下面描述了一个很简单的Console application, 全部的组件都经过Castle Windsor容器进行构造器注入:
//register var container = new WindsorContainer(); container.Register(Component.For<IParser>().ImplementedBy<Parser>()); container.Register(Component.For<IWriter>().ImplementedBy<Writer>()); container.Register(Component.For<Application>()); //resolve var application = container.Resolve<Application>(); application.Execute("hel--lo, wor--ld"); //release container.Release(application);
这个例子向咱们展现了一个最简单的依赖注入使用方式,register全部组件,resolve客户端程序,最后的release步骤向咱们展现了若是显示从DI容器获得一个对象,应该显示释放该组件。这一步在大多数状况下并非必须的,可是在特定场景下会发生内存泄漏。
下面的解决方案描述了一个典型的应用程序分层结构,该分层结构用来描述如何使用Catle windsor
进行依赖注入,注意:这并非一个合理的领域驱动案例,例如我将Domain
模型引用到了Application
或者ApplicationService
程序集中。
处在项目最底层的Repository
程序集定义了一组UserRepository
及其接口IUserRepository
,这样的一个组件如何注册在Windsor Container中呢?Castle提供了一种叫作WindsorInstaller
的机制:
public class RepositoryInstaller:IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register(Component.For<IUserRepository>().ImplementedBy<UserRepository>().LifestyleScoped()); } }
该Installer利用Fluent Api定义了IUserRepository
和UserRepository
的对应关系,相对于Java中的Spring框架提供的代码注册方式,该方案的优越之处是显而易见的。
另外的重点在于该Installer此时并无执行,只有当客户端调用此Installer时,该组件才真真注册进容器。这一点很关键,咱们后面还会提到。
接下来的ApplicationService
层使用了Repository
的抽象,一个典型的使用片段以下:
public class UserApplicationService : IUserApplicationService { private readonly IUserRepository _userRepository; public UserApplicationService(IUserRepository userRepository) { _userRepository = userRepository; } public void Register(User user) { _userRepository.Save(user); } //..... }
咱们经过构造器注入的方式注入了IUserRepository
,同时,做为Service层,它也拥有本身的Installer:
public class ApplicationServiceInstaller:IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register( Classes.FromThisAssembly().BasedOn<IApplicationService>().WithServiceDefaultInterfaces().LifestyleScoped()); } }
上面的例子示范了如何经过Castle提供的高级api来实现将该程序集中全部继承于IApplicationService
的组件和其默认接口一次性所有注册到DI容器中。
好比UserApplicationService
和IUserApplicationService
,以及将来将要实现的OrderApplicationService
以及IOrderApplicationService
。
接下来到客户端程序集Application层,Application做为使用ApplicationService
程序集的客户端,他才拥有将组件注册进DI容器的能力,咱们定义一个ApplicationBootstrap
来初始化DI容器并注册组件:
public class ApplicationBootstrap { public static IWindsorContainer Container { get; private set; } public static IWindsorContainer RegisterComponents() { Container=new WindsorContainer(); Container.Install(FromAssembly.This()); Container.Install(FromAssembly.Containing<ApplicationServiceInstaller>()); Container.Install(FromAssembly.Containing<RepositoryInstaller>()); return Container; } }
注意Container.Install(...)
方法将执行不一样应用程序的Installer,此时组件才真真注册进DI容器。该实例展现了如何正确的使用依赖注入框架:
本文提供的源码中所含的WebApplicationSample
项目演示了如何经过自定义WindsorControllerFactory
来实现mvc的依赖注入,经过自定义WindsorCompositionRoot
实现web api的依赖注入。
asp.net core实现了一个还算简单的DI容器DenpendencyInjection,感兴趣的同窗能够阅读其源码。
本文所描述的案例提供下载,点击下载