Prism 文档 第三章 管理组件之间的依赖关系
第3章:管理组件之间的依赖关系
基于Prism库的复合应用程序可能包含许多松耦合的类型和服务。他们需要提供内容和接收基于用户行为的通知。因为他们是松散耦合的,他们需要一种方式进行互动和相互沟通来提供所需的业务功能。
为了集中这些不同的块,基于Prism库的应用程序依赖于依赖注入容器。依赖注入容器通过提供设施去实例化类的实例和管理他们基于容器配置的寿命来减少对象之间的依赖耦合。在对象的创建过程中,容器注入对象需要的任何依赖。如果那些依赖尚未创建,容器首先创建和解析他们的依赖关系。在某些情况下,容器本身是作为一个依赖被解析。例如,使用Unity Application Block(Unity)作为容器,容器注入模块,所以他们可以在容器中注册他们的View和服务。
下面是使用容器的一些有利之处:
- 容器移除对组件的需要以致可以找到它的依赖或管理他们的寿命。
- 容器在不影响组建的情况下,允许实现依赖的交换。
- 容器允许模拟依赖以致有利于可测性。
- 容器允许新的组件被容易地添加到系统中以致增加可维护性。
在基于Prism库的应用程序中,容器有特定的优势:
- 当模块被加载时,容器予其注入模块依赖。
- 容器被用于注册和解析View models 和 views
- 容器可以穿件view models 和 注入 view。
- 容器注入组件服务,比如说区域管理和事件集合
- 容器用于注册具有特定模块功能的特定模块服务,
Note:在Prism指导中的一些样品依靠Unity Application Block(Unity)作为容器。其他代码案例,例如Modularity QuickStarts,使用Managed Extensibility Framework (MEF)。Prism库本身不是特定的容器,你可以在其他容器中使用它的服务与模式,如Castle Windsor,StructureMap和Spring.NET。
关键的抉择:选择一个依赖注入容器
Prism库为依赖注入容器提供了两个选择:Unity或MEF。Prism是可扩展的,从而允许其他容器被用来代替一点点工作。Unity和MEF都为依赖注入提供相同的基本功能,尽管他们非常不同。由两个容器提供的一些功能包括以下:
- 它们两个都在容器中注册类型。
- 它们两个都在容器中注册实例。
- 它们两个都需要创建注册类型的实例。
- 它们都注入注册型实例到构造函数。
- 它们都注入注册型实例到属性。
- 它们都有为标记类型和需要管理的依赖关系声明属性。
- 它们都解析一个对象图的依赖关系
Unity具有的而MEF所不具有的一些功能:
- 它解析没有注册的具体类型。
- 它解析开放泛型。
- 它采用侦听去捕获调用对象并且向目标对象添加额外的功能。
MEF具有的而Unity所不具有的一些功能:
- 它在一个目录中发现组件。
- 它使用XAP文件下载和组件发现。
- 它将属性和集合作为一种新类型的发现。
- 它自动导出派生类型。
- 它被部署在.NET Framework下。
容器间的能力是不同的,工作也是不同的,但Prism库配合任何容器的工作并且提供类似的功能。当考虑使用哪种容器,记住前面提到的能力,确定适合您的更好的方案。
考虑使用容器
在使用容器之前,你应该考虑下列所述:
- 考虑使用容器的时候是否它适合用来注册和解析组件。
(1) 考虑在容器中注册和用容器解析实例对性能的影响是否可以接受。
(2) 如果有许多或深层次的依赖,创造的成本会显著增加。
(3) 如果组件没有任何依赖或不依赖其他类型,没。
(4) 如果组件有单一的对型积分和永远不变的一套依赖关系,必要把它放在容器里。
2. 考虑一个组件的寿命是否应注册为一个单例或实例
(1) 如果组件是一个全局的作为一个单一资源的资源管理器的服务,如日志服务,你可能想将它注册成为单例。
(2) 如果组件向多个消费者提供共享状态,你可能想将它注册成为单例。
(3) 如果每次正在被注入的对象需要有一个它注入过的新实例,也就是依赖对象所需要的那个,那么将其注册为非单例。例如,每个视图可能需要一个视图模型的一个新实例。
3. 考虑你是否想通过代码或配置去配置容器:
(1) 如果你想集中管理所有不同的服务,那就通过配置来配置容器。
(2) 如果你想有条件的注册特定的服务,那就通过代码配置容器。
(3) 如果你具有模块级的服务,考虑通过代码来配置容器,以至于那些服务只有在被加载的时候才会被注册
Note:一些容器,比如MEF,不能通过配置文件来配置,必须通过代码来配置。
核心方案
容器被用于两个主要目的----注册和解析
注册
在你将依赖项注入到一个对象之前,依赖关系的类型必须在容器中注册。注册类型通常是通过容器接口和具体实现该接口的类型。主要有两种类型和对象注册的方式:通过代码或通过配置。具体的根据容器的不同而不同。
典型的,在容器中有两种方式通过代码注册类型与对象:
1.你可以用容器注册一个类型或映射,在适当的时候,容器将建立你指定类型的一个实例。
2.在容器中,你可以注册一个现有的对象实例作为一个单例,容器将返回该对象的引用。
用Unity容器注册类型
在初始化过程中,一个类型可以被注册为其他类型,比如视图和服务。注册允许他们的依赖性属性通过容器被提供,并且允许他们被其他类型访问。为了达到这个,类型需要有一个容器注入到模块构造函数。下列代码展示在Commanding QuickStart中的OrderModule类型注册了一个类型。
1 public class OrderModule : IModule
2 {
3 public void Initialize()
4 {
5 this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager());
6 ...
7 }
8 ...
9 }
Note:和配置相比,在代码中注册的有利之处在于,只有模块被加载的时候注册才会发生。
用MEF来注册类型
为了使用容器注册类型,MEF使用基于属性的系统。自然而然,添加类型注册到容器是很简单的:它需要附加【Export】属性到类型中,如下所示:
1 [Export(typeof(ILoggerFacade))]
2 public class CallbackLogger: ILoggerFacade
3 {
4 }
当使用MEF时的另一个选择是创建一个类的实例并且用容器注册该特定实例。在Modularity for Silverlight with MEF QuickStart 中的QuickStartBootstrapper展示了一个用ConfigureContainer方法的例子
protected override void ConfigureContainer()
{
base.ConfigureContainer();
// Because we created the CallbackLogger and it needs to
// be used immediately, we compose it to satisfy any imports it has.
this.Container.ComposeExportedValue<CallbackLogger>(this.callbackLogger);
}
解析
类型被注册之后,它可以作为一个依赖属性被解析或者注入。当一个类型已被解析,容器需要去创建一个新的实例,并注入依赖性属性到这些实例中。
通常而言,当一个类型被解析,会发生下列三件事之一:
- 如果类型没有被注册容器会抛出一个异常。
Note:包括Unity在内的一些容器允许解析一个具体的尚未被注册的类型。
2.如果类型被注册为一个单例,容器将返回单例的实例。如果类型被第一次调用,容器会创建它并为将来的调用一直保留。
3.如果类型没有被注册为一个单例,容器将返回一个新的实例。
Note:默认情况下,用MEF注册的类型是单例并且容器保留对象的引用。在Unity中,新的对象实例在默认情况下返回,容器不保留对该对象的引用。
用Unity解析实例
来自Commanding QuickStart的下列代码事例展示了 OrdersEditorView和OrdersToolBar 视图从容器关联他们到相应的区域处被解析。
1 public class OrderModule : IModule
2
3 {
4
5 public void Initialize()
6
7 {
8
9 this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager());
10
11
12
13 // Show the Orders Editor view in the shell's main region.
14
15 this.regionManager.RegisterViewWithRegion("MainRegion",
16
17 () => this.container.Resolve<OrdersEditorView>());
18
19
20
21 // Show the Orders Toolbar view in the shell's toolbar region.
22
23 this.regionManager.RegisterViewWithRegion("GlobalCommandsRegion",
24
25 () => this.container.Resolve<OrdersToolBar>());
26
27 }
28
29 ...
30
31 }
OrdersEditorPresentationModel 构造函数包含下列依赖属性,当它被解析时,依赖性属性被注入。
1 public OrdersEditorPresentationModel( IOrdersRepository ordersRepository, OrdersCommandProxy commandProxy )
2
3 {
4
5 this.ordersRepository = ordersRepository;
6
7 this.commandProxy = commandProxy;
8
9
10
11 // Create dummy order data.
12
13 this.PopulateOrders();
14
15
16
17 // Initialize a CollectionView for the underlying Orders collection.
18
19 #if SILVERLIGHT
20
21 this.Orders = new PagedCollectionView( _orders );
22
23 #else
24
25 this.Orders = new ListCollectionView( _orders );
26
27 #endif
28
29 // Track the current selection.
30
31 this.Orders.CurrentChanged += SelectedOrderChanged;
32
33 this.Orders.MoveCurrentTo(null);
34
35 }
除了在前面的代码显示的构造函数注入,Unity也允许属性注入。当对象被解析时,任何具有【依赖】性质的属性都是被自动解析并注入。
用MEF解析实例
下面的代码示例演示在Modularity forSilverlight with MEF QuickStart中引导程序如何获得外壳的一个实例。代码可以请求接口的实例,而不是请求一个具体类型.
1 protected override DependencyObject CreateShell()
2
3 {
4
5 return this.Container.GetExportedValue<Shell>();
6
7 }
在任何由MEF解析的类中,你也可以使用构造函数注入,如下面的来自ModuleA in the Modularity for Silverlight with MEF QuickStart的代码示例所示,其中有一个ILoggerFacade和IModuleTracker注入。
1 [ImportingConstructor]
2
3 public ModuleA(ILoggerFacade logger, IModuleTracker moduleTracker)
4
5 {
6
7 if (logger == null)
8
9 {
10
11 throw new ArgumentNullException("logger");
12
13 }
14
15 if (moduleTracker == null)
16
17 {
18
19 throw new ArgumentNullException("moduleTracker");
20
21 }
22
23 this.logger = logger;
24
25 this.moduleTracker = moduleTracker;
26
27 this.moduleTracker.RecordModuleConstructed(WellKnownModuleNames.ModuleA);
28
29 }
另外一个功能就是依赖注入,正如在ModuleTracker类(来自Modularity for Silverlight with MEF QuickStart)中所示,这个类存在一个ILoggerFacade注入的实例.
1 [Export(typeof(IModuleTracker))]
2
3 public class ModuleTracker : IModuleTracker
4
5 {
6
7 // Due to Silverlight/MEF restrictions, this must be public.
8
9 [Import] public ILoggerFacade Logger;
10
11 }
Note:在Silverlight中,导入属性和域必须是public.
在Prism中,使用依赖注入容器和服务
通常被称为是“容器”的依赖注入容器,是用来满足组件之间的依赖关系. 满足这些依赖关系通常涉及到的注册和解析.Prism为Unity容器和MEF提供支持,但不是特定容器. 由于库通过IServiceLocator接口访问容器,所以容器可以被取代。要做到这一点,你的容器必须实现IServiceLocator接口。通常,如果要更换容器,您还需要提供你自己的特定容器的引导程序. IServiceLocator接口被定义在Common Service Locator Library中。这是开源的并提供一个抽象 IoC(控制反转)的容器,如依赖注入容器,和服务定位器。使用这个库的目的是在未绑到一个特定的实现的情况下,利用IoC和服务定位。
Prism库提供的UnityServiceLocatorAdapter和MefServiceLocatorAdapter。两个适配器通过扩展ServiceLocatorimplBase类型实现ISeviceLocator接口。下面的插图显示了类层次。
The Common Service Locator implementations in Prism
虽然Prism库不参考或依赖于一个特定的容器,对于应用程序依赖于一个特定的容器却是典型的。这意味着,对于依赖于容器的一个具体应用是合理的,但Prism库不是直接依赖容器。例如,股票交易日数的快速入门包括棱镜依靠统一为容器。其他样品和快速入门依赖MEF。包括Prism在内的the Stock Trader RI and several of the QuickStarts 依赖于Unity作为容器. 其他的例子的QuickStarts依赖于MEF.
IServiceLocator
下列代码展示了IServiceLocator接口
1 public interface IServiceLocator : IServiceProvider
2
3 {
4
5 object GetInstance(Type serviceType);
6
7 object GetInstance(Type serviceType, string key);
8
9 IEnumerable<object> GetAllInstances(Type serviceType);
10
11 TService GetInstance<TService>();
12
13 TService GetInstance<TService>(string key);
14
15 IEnumerable<TService> GetAllInstances<TService>();
16
17 }
服务定位器是用下面的代码所示的扩展方法在Prism中被扩展的。你可以看到,IServiceLocator只用于解析,这意味着它是用来获取一个实例而不用于注册。
1 public static class ServiceLocatorExtensions
2
3 {
4
5 public static object TryResolve(this IServiceLocator locator, Type type)
6
7 {
8
9 try
10
11 {
12
13 return locator.GetInstance(type);
14
15 }
16
17 catch (ActivationException)
18
19 {
20
21 return null;
22
23 }
24
25 }
26
27
28
29 public static T TryResolve<T>(this IServiceLocator locator) where T: class
30
31 {
32
33 return locator.TryResolve(typeof(T)) as T;
34
35 }
36
37 }
TryResolve扩展方法-----Unity容器不支持----返回类型的一个实例,该类型如果没有被注册,将会被解析;否则返回null。
在模块加载时, ModuleInitializer使用 IServiceLocator来解析模块,如下面的代码示例所示。
1 IModule moduleInstance = null;
2
3 try
4
5 {
6
7 moduleInstance = this.CreateModule(moduleInfo);
8
9 moduleInstance.Initialize();
10
11 }
12
13 ...
1 protected virtual IModule CreateModule(string typeName)
2
3 {
4
5 Type moduleType = Type.GetType(typeName);
6
7 if (moduleType == null)
8
9 {
10
11 throw new ModuleInitializeException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedToGetType, typeName));
12
13 }
14
15
16
17 return (IModule)this.serviceLocator.GetInstance(moduleType);
18
19 }
考虑使用IServiceLocator
IServiceLocator并不意味着是通用的容器。容器的使用具有不同的语义,这往往使决定为什么那个容器被选择。因此,Stock Trader RI直接使用Unity而不是使用IServiceLocator。这是为你的应用程序的开发所推荐的方法。
在下列解决方法中,使用IServiceLocator或许是合适的:
- 你是一个独立软件供应商(ISV),设计需要支持多个容器第三方服务.
- 你正在设计一个被应用于组织的服务,这个组织使用多容器.
More Infomation
与容器相关的信息,如下所示:
- "Unity Application Block" on MSDN: http://www.msdn.com/unity
- Unity community site on CodePlex: http://www.codeplex.com/unity
- "Managed Extensibility Framework Overview" on MSDN: http://msdn.microsoft.com/en-us/library/dd460648.aspx
- MEF community site on CodePlex: http://mef.codeplex.com/
- "Inversion of Control containers and the Dependency Injection pattern" on Martin Fowler's website: http://www.martinfowler.com/articles/injection.html
- "Design Patterns: Dependency Injection" in MSDN Magazine: http://msdn.microsoft.com/en-us/magazine/cc163739.aspx
- "Loosen Up: Tame Your Software Dependencies for More Flexible Apps" in MSDN Magazine: http://msdn.microsoft.com/en-us/magazine/cc337885.aspx
- Castle Project: http://www.castleproject.org/container/index.html
- StructureMap: http://structuremap.sourceforge.net/Default.htm
- Spring.NET: http://www.springframework.net/