CAL学习之二:容器(翻译)
容器:
基于CAL实现的程序,都是有一些松耦合的module构成,这些模块需要和Shell交互来实现数据的表现和相应用户的操作。因为他们都是松耦合的,所以他们需要一种相互交互和通讯的方式来实现所需要的业务功能。
为了配合这些不同的模块,基于CAL的应用程序使用了依赖注入的容器。依赖注入的容器可以减少对象之间的依赖,容器可以帮助我们实例化和维护一个类的实例的生命周期。这些都是依据对容器的配置。在一个对象的创建过程中,容器把这个对象需要的实例都注入进去。如果它需要的实例还没有创建,容器会先去创建再注入。有些时候,容器要把自己注入到对象中。比如一个module需要注入一个容器,这样module就可以把自己的view和service注册到容器中。
使用容器有以下好处:
- 容器省掉你下边的麻烦:你需要使用一个组件还要弄清楚它的依赖关系,以及创建和销毁它。
- 使用了容器,当你的类需要的组件的实现改变时,你的类不需要改变。
- 使用了容器,方便了你测试,因为你可以用一个假的依赖。
- 容器使系统易于维护,因为一个组件可以很容易的被加入进来。
对于CAL来说:容器有以下好处:
- 容器把一个module的依赖在他载入时注入。
- 容器被用来注册presenter和view。
- 容器创建Presenter和model,并把它们注入到view中。
- 容器可以注入服务,比如 regionmanager和event aggregator
- 容器可以用来注册模块相关的service。
下边的代码演示了injection是如何工作的,当PositionModule被container创建,它就会被注入regionManager和container。在RegisterViewsAndServices方法中,service,view和presenter都被注册。
publicPositionModule(IUnityContainer container, IRegionManager regionManager)
{
_container = container;
_regionManagerService = regionManager;
}
public voidInitialize()
{
RegisterViewsAndServices();
...
}
protected voidRegisterViewsAndServices()
{
_container.RegisterType<IAccountPositionService,AccountPositionService>(new ContainerControlledLifetimeManager());
_container.RegisterType<IPositionSummaryView,PositionSummaryView>();
_container.RegisterType<IPositionSummaryPresentationModel,PositionSummaryPresentationModel>();
...
}
使用Container
Container主要有两种用途:注册和解析。
注册:
在你要对一个对象的依赖进行注入之前,依赖的类型必须被注册到container中,注册一个类型需要传给container一个接口和实现这个接口的具体类型。你可以通过代码也可以通过配置来进行注册。
通过代码你有两种方法来注册类型和对象到container中。
- 你可以注册一个类型或者一个映射到container,在合适的时候,container会自己创建一个你指定类型的实例。
- 你可以注册一个已经生成的对象到container,container将会返回一个这个对象的引用。
this.container.RegisterType<IEmployeesController,EmployeesController>();
上边的代码用第一种方法来实现注册。
注册到container中的类型有一个生存期,可以是singleton或者是instance(每次都创建一个新的)。生存期可以在注册时或者在配置文件中指定。默认的生存期由container的实现决定,比如Unity container默认的是instance模式。
解析(Resolving)
当一个类型被注册后,就可以被解析或者作为一个依赖注入。
一般情况下,当一个类型解析出来,会有下边三种情况:
1.这个类型没有注册,container会抛出异常。
2.如果这个类型被注册为一个singleton模式,container将会返回一个singleton实例,如果这是第一次请求,就创建一个,并且保留这个实例,以备后用。
- 如果这个类型不是singleton的,就生成一个实例返回,并不保留对实例的引用。
下边的代码就是从container中解析一个类型:
EmployeesPresenterpresenter = this.container.Resolve<EmployeesPresenter>();
但是EmployeesPresenter的构造函数有下边的依赖,这些依赖在类型解析时会被注入实例。
publicEmployeesPresenter(IEmployeesView view, IEmployeesListPresenter listPresenter, IEmployeesControlleremployeeController)
{
this.View = view;
this.listPresenter = listPresenter;
this.listPresenter.EmployeeSelected +=newEventHandler<DataEventArgs<BusinessEntities.Employee>>(this.OnEmployeeSelected);
this.employeeController = employeeController;
View.SetHeader(listPresenter.View);
}
什么时候使用容器?
考虑使用容器来进行注册和解析是否合适。
你的应用是否能接受注册和解析所消耗的性能。因为容器时利用了反射。
如果依赖过多或者层次多深,性能消耗会明显增加。
如果一个组件没有任何依赖,或者其他类型也不依赖他,那么把它放到容器中就没有什么意义。
考虑对象的生存期,是否用singleton?
如果一个组件是一个全局的服务,比如log,我们将它注册成单例模式。
如果一个组件对多个客户提供状态共享,你可以将它注册成单例模式。
如果有的依赖每次有需要一个新的实例,你可以将它注册成一个非单例模式。
考虑你是通过代码还是配置文件来配置container。
如果你想集中管理不同的服务,那就通过配置文件来配置。
如果你想根据不同的情况注册不同的service,那就要通过代码来配置。
如果你有模块级别的服务,那么就用代码来注册这些服务,因为这样就可以只在模块载入时再注册服务。