Prism研究(for WPF & Silverlight)7.View Injection和View Discovery
一切从这里开始。Prism文档对于这两个概念的解释着实有些扯淡,甚至自相抵牾,还是不要看了,乖乖听额讲吧。
我们晓得,每个Module中,实现了IModule接口的Module类,都要实现该接口的Initialize方法,一方面要注册一些自定义的接口和实现了该接口的类,为接下来的依赖注入做准备;另一方面,就是为Region加载View并显示(初始化View)。
加载View的方式,分为View Injection和View Discovery两类,各有千秋。
1. View Injection,指的是RegionManager,它的属性Regions,这是一个集合类RegionCollection,同时还实现了基于RegionName的内部索引。于是,我们可以这样编写代码:
View-first:
this.regionManager.Regions["MainRegion"].Add(new HelloWorldView());
Presenter-first:
this.regionManager.Regions["MainRegion"].Add(this.container.Resolve<HelloWorldPresenter>().View);
有关View-first和Presenter-first的概念,我们在下一节中专门介绍。
View Injection的视图如下,有点像中介者模式吧,那个Registry就是一个Mediator:
注意,View Injection只适用于RegionManager,不适用于RegionViewRegistry,原因么,看我下面的分析。
2. View Discovery,指的是RegionViewRegistry的RegisterViewWithRegion方法,有2个重载:
public void RegisterViewWithRegion(string regionName, Type viewType)
{
this.RegisterViewWithRegion(regionName, () => this.CreateInstance(viewType));
}
public void RegisterViewWithRegion(string regionName, Func<object> getContentDelegate)
{
this.registeredContent.Add(regionName, getContentDelegate);
this.OnContentRegistered(new ViewRegisteredEventArgs(regionName, getContentDelegate));
}
我们看到,第1个方法是基于第2个方法的,就是把RegionName和一个delegate(用于创建View的一个实例)同时注册到字典ListDictionary<string, Func<object>>和委托链WeakDelegatesManager中。
于是我们可以这样编写代码:
View-first:
this. RegionViewRegistry.RegisterViewWithRegion("MainRegion", typeof(Views.HelloWorldView));
Presenter-first:
this. RegionViewRegistry.RegisterViewWithRegion("MainRegion",
() => this.container.Resolve<IHelloWorldPresenter>().View);
有关View-first和Presenter-first的概念,我们在下一节中专门介绍。
View Discovery的视图如下,看这个图让我想起了GOF中的注册工厂:
Prism文档中P18说到,View Discovery适用于V-first,而View Injection同时适用于V-first和P-first。这是不恰当的。看了我上面的分析和代码,就会发现View Discovery也有以P-first方式实现的,而且也是很常用的。
View Injection和View Discovery,本来就是2个不同类——RegionManager和RegionViewRegistry的不同实现方式,连存储结构都不同,前者是集合,后者是字典。相对而言,前者的实现更直接一些(来一个招呼一个),后者比较绕(先对号入座,然后再用到谁招呼谁)。
存储结构的不同,使得二者在性能上有很大差异。RegionManager的Regions属性是一个集合,所以可以手动添加或移除Region中的View(使用集合的Add或Remove方法),尤其是移除功能,可以释放不再使用的内存。
相对而言,RegionViewRegistry在内部使用字典来存储View,它的RegisterViewWithRegion方法就是把View添加到这个字典中,但是,由于这个字典是私有的,不对外暴露,所以我们无法使用它的Remove方法来移除View。这是因为,我们存储到字典中的View都是弱引用,再回顾一下:
this. RegionViewRegistry.RegisterViewWithRegion("MainRegion", typeof(Views.HelloWorldView));
这里typeof(Views.HelloWorldView)就是一个弱引用,我们知道,.NET会自动回收不再使用的内存,所以,理想情况下,不再使用这个View时,该View所占的内存就会被自动回收了。但是,自动回收内存在什么时候发生呢?没人知道。所以,系统越来越慢,就是因为RegionViewRegistry的这个方法导致的。
既然不好用,那为什么还要设计出RegionViewRegistry呢?
说起来话长了。这要从Prism的第1版说起。话说,那时Prism中只有View Injection机制,也就是说,只有RegionManager类。后来,大家发现RegionManager类也有不好的地方,就是在嵌套Region的时候,每一级Region中都要写点Regions[“RegionName”].Add方法,而且还要care层次间的关系,是不是觉得很麻烦?而且Prism经常会报错说找不到RegionName,以致于抛出异常(关于这一点,请参见我的另一篇文章)。为了方便开发,p&p小组在Prism第2版中提供了RegionViewRegistry类,及其RegisterViewWithRegion方法,于是我们可以在任何地方使用该方法,而不需要在各自的View中编写相应的代码。但是,简单是要付出代价的,那就是性能,上文已经分析过。
同时,Prism框架把RegisterViewWithRegion也作为扩展方法添加到了RegionManager类中,实际上还是调用RegionViewRegistry类的RegisterViewWithRegion方法。于是,形成了一个组合关系。
本来设计思想是蛮好的,可以屏蔽RegionViewRegistry类,对Client而言,只有RegionManager类,它同时具有RegisterViewWithRegion方法和Regions[“RegionName”]属性(前者是弱引用方式,后者是强引用方式)。
但是,不知是微软p & p的那个DPE大脑进水,在Prism文档和示例又同时使用了RegionViewRegistry的RegisterViewWithRegion方法。这就违背了设计的初衷。于是,我们糊涂了,不知道什么时候该使用哪一个类的哪一个方法,甚至开始怀疑人生。