Prism初研究之依赖管理


Prism初研究之依赖管理

注意:Prism框架本身不提供指定的依赖注入容器,你可以使用其它的依赖注入容器,比如:Castle Windsor,StructureMap,和Spring.NET。示例中有使用Unity(Unity Application Block)作为容器的,也有使用MEF(Managed Extensibility Framework)的。

关键决定:选择DI容器

Prism提供了Unity和MEF两种可供选择的DI容器,如果选用其它的容器需要多做一些工作。

  • Unity和MEF都提供了以下一些功能:
    • 都使用容器注册类型;
    • 都使用容器注册实例;
    • 都使用命令式地方式创建注册类型的实例;
    • 都将依赖注入到构造函数中;
    • 都将依赖注入到属性中;
    • 都支持特性标注的类型依赖注入;
    • 都解析对象图中的依赖关系。
  • Unity特有的功能:
    • 能够解析具体类型而无需注册;
    • 能解析开放泛型;
    • 它截取对对象的调用,并给目标对象添加额外的功能。
  • MEF特有的功能:
    • 能发现文件夹中的程序集;
    • 使用XAP文件来下载和装配发现;
    • 它将发现的属性和集合重写为新的类型;
    • 自动导出派生类型;
    • 和.NET框架一起部署。

考虑使用容器

考虑在选择前:

  • 是否使用容器来注册和解析组件:
    • 考虑性能开销是否可接受,因为使用反射创建实例开销比较大;
    • 如果有很多或者深度的依赖关系,创建实例的开销会显著增加;
    • 如果一个组件没有依赖,或者也不依赖于其它类型,不用将它放入容器中;
    • 如果一个组件有一组依赖关系,并且依赖关系不会改变,不用将它放入容器中。
  • 考虑一个组件的生命周期,来决定注册为单例还是一个实例:
    • 如果组件是一个全局的服务,作为一个单一的资源来进行管理,应该将它注册为单例。比如日志服务;
    • 如果一个组件提供共享的状态给多个客户端,应该将它注册为单例;
    • 如果一个对象每次被注入时需要一个新的实例,应该将它注册为实例。
  • 考虑通过代码还是配置文件来配置容器:
    • 如果想要集中管理所有不同的服务,就使用配置文件来配置容器;
    • 如果想要按照条件注册指定的服务,就需要通过编码来配置容器;
    • 如果有模块级别的服务,就需要通过编码来配置容器,防止注册发生在模块加载前。

      有一些DI容器不支持配置文件,比如MEF。

核心情景

DI容器可以达到两个不低,类型的注册和依赖解析。

类型注册

一般来讲,有两种类型的注册方式:

  • 在容器中注册类型或者映射,在合适的时候,返回指定类型的一个实例;
  • 在容器中注册一个已经存在的对象实例,作为单例。容器会返回一个单例对象的引用。

使用Unity容器注册类型

在初始化过程中,一个类型允许注册其它的类型,比如视图类和服务类。注册使依赖的请求可以通过容器来实现。因此,模块的构造函数需要拥有容器的依赖注入。

public class OrderModule : IModule
{
    private readonly IRegionManager  regionManager;
    private readonly IUnityContainer container;

    public OrderModule( IUnityContainer container, IRegionManager regionManager )
    {
        this.container     = container;
        this.regionManager = regionManager;
    }

    public void Initialize()
    {
        this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager());

        // Show the Orders Editor view in the shell's main region.
        this.regionManager.RegisterViewWithRegion( "MainRegion", () => this.container.Resolve<OrdersEditorView>() );

        // Show the Orders Toolbar view in the shell's toolbar region.
        this.regionManager.RegisterViewWithRegion( "GlobalCommandsRegion", () => this.container.Resolve<OrdersToolBar>() );
    }
}

使用代码注册比配置文件注册的好处是,可以保证只有模块加载之后才会发生注册。

使用MEF容器注册类型

MEF可以使用特性系统来完成类型注册到容器:

[Export(typeof(ILoggerFacade))]
public class CallbackLogger: ILoggerFacade
{
}

MEF还可以通过注册一个特定的实例:

protected override void ConfigureContainer()
{
    base.ConfigureContainer();
    this.Container.ComposeExportedValue<CallbackLogger>(this.callbackLogger);
}

注意:如果使用MEF容器,推荐使用特性系统来完成类型注册。

依赖解析

当一个类型依赖需要被解析时,会发生三件事:

  1. 如果类型没有被注册,容器会抛出异常。

    注意:有一些容器(包括Unity),允许未被注册的具体类型。

  2. 如果类型被注册为单例,容器会返回单例实例,如果类型是第一次被请求,容器会创建这个单例并一直维护它。

  3. 如果类型未被注册为单例,容器会返回一个新的实例对象。

    注意:MEF默认注册为单例,并且容器会维护一个单例的引用。Unity默认返回一个新的实例对象,并且容器不会维护这些实例的引用。

Unity实例解析

示例:Commanding QuickStart

  • 容器解析OrdersEditorView和OrdersToolBar视图的依赖关系:
// OrderModule.cs
public class OrderModule : IModule
{
    public void Initialize()
    {
        this.container.RegisterType<IOrdersRepository, OrdersRepository>(new ContainerControlledLifetimeManager());
        //在main region显示Orders Editor 视图
        this.regionManager.RegisterViewWithRegion("MainRegion", () => this.container.Resolve<OrdersEditorView>());

        //在工具栏Region显示工具栏
        this.regionManager.RegisterViewWithRegon("GlobalCommandsRegion", () => this.container.Resolve<OrdersToolBar>());
    }
}
  • OrdersEditorViewModel的构造函数依赖注入:
public OrdersEditorViewModel(IOrdersRepository ordersRepository, OrdersCommandProxy commandProxy)
{
    this.ordersRepository = ordersRepository;
    this.commandProxy     = commandProxy;

    //创建虚拟的订单数据
    this.PopulateOrders();

    this.Orders = new ListCollectionView( _orders );

    //跟踪目前的选择
    this.Orders.CurrentChanged += SelectedOrderChanged;
    this.Orders.MoveCurrentTo(null);
}
  • Unity还可以使用属性来进行依赖注入,任何拥有[Dependency]特性的属性都能够进行自动的依赖解析

MEF实例解析

示例:Modularity with MEF QuickStart

  • 请求具体的类型
protected override DependencyObject CreateShell()
{
    return this.Container.GetExportedValue<Shell>();
}
  • 通过构造函数依赖注入:示例中的ModuleA
[ImportintConstructor]
public ModuleA(ILoggerFacade logger, IModuleTracker moduleTracker)
{
    if(logger == null)
    {
        throw new ArgumentNullException("logger");
    }
    if(moduleTracker == null)
    {
        throw new ArgumentNullException("moduleTracker");
    }
    this.logger = logger;
    this.moduleTracker = moduleTracker;
    this.moduleTracker.RecordModuleConstructed(WellKnownModuleNames.ModuleA);
}
  • 通过属性进行依赖注入:ModuleTracker
[Export(typeof(IModuleTracker))]
public class ModuleTracker : IModuleTracker
{
    [Import]
    private ILoggerFacade Logger;
}

在Prism中使用依赖注入容器

Prism框架提供Unity和MEF两种依赖注册容器的支持,但是这两种DI容器并不是指定的。因为框架通过IServiceLocator接口来访问DI容器,所以DI容器可以被替换,只需要实现IServiceLocator接口即可。通常如果要替换DI容器,同时需要提供自己指定的Bootstrapper。IServiceLocator接口定义在Microsoft.Practices.ServiceLocation类库中(通用服务类库),这是一个开源的控制反转容器(IoC
)。
Prism提供UnityServiceLocatorAdapter和MefServiceLocatorAdapter,它们都继承自ServiceLocatorImplBase类型,后者实现了IServiceLocator接口。
Prism中IServiceLocator的通用实现
Prism并不指定DI容器,而是由特定的应用来指定。

IServiceLocator

IserviceLocator接口:

public interface IServiceLocator : IServiceProvider
{
    object GetInstance(Type serviceType);
    object GetInstance(Type serviceType, string key);
    IEnumerable<object> GetAllInstances(Type serviceType);
    TService GetInstance<TService>();
    TService GetInstance<TService>(string key);
    IEnumerable<TService> GetAllInstances<TService>();
}

IServiceLocator接口在Prism中通过扩展方法进行了扩展,你可以看到IServiceLocator接口只用来进行依赖解析,并不能用来进行类型注册。扩展方法如下:

public static class ServiceLocatorExtensions
{
    public static object TryResolve(this IServiceLocator locator, Type type)
    {
        if (locator == null) throw new ArgumentNullException("locator");

        try
        {
            return locator.GetInstance(type);
        }
        catch (ActivationException)
        {
            return null;
        }
    }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
    public static T TryResolve<T>(this IServiceLocator locator) where T : class
    {
        return locator.TryResolve(typeof(T)) as T;
    }
}

TryResolve扩展方法(Unity不支持),返回一个注册类型的实例,否则返回null。
ModuleInitializer使用IServiceLocator接口来解析模块的加载。例如:

// ModuleInitializer.cs - Initialize()
IModule moduleInstance = null;
try
{
    moduleInstance = this.CreateModule(moduleInfo);
    moduleInstance.Initialize();
}
...

// ModuleInitializer.cs - CreateModule()
protected virtual IModule CreateModule(string typeName)
{
    type moduleType = Type.GetType(typeName);
    if (moduleType == null)
    {
        throw new ModuleInitializeExcetpion(string.Format(CultureInfo.CurrentCulture, Properties.Resources.FailedToGetType, typeName));
    }

    return (IModule)this.serviceLocator.GetInstance(moduleType);
}

扩展阅读

DI容器的相关资料:

· Unity Application Block on MSDN.

· Unity community site on CodePlex.

· Managed Extensibility Framework Overview on MSDN.

· MEF community site on CodePlex.

· Inversion of Control containers and the Dependency Injection pattern on Martin Fowler’s website.

· Design Patterns: Dependency Injection in MSDN Magazine.

· Loosen Up: Tame Your Software Dependencies for More Flexible Apps in MSDN Magazine.

· Castle Project

· StructureMap

· Spring.NET





posted @   qianzi  阅读(1951)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示