最近3周一直都在学习Prism,留下点笔记,梳理下思路......
要点1:
在CAL中,模块是实现Imodule接口的类,此接口仅包含一个方法,成为称为Initializae。如果把引导程序看做应用程序的Main方法,那么Initialize方法就是每个模块的Main方法。
要点2:
对于Module中的Initialize方法中,提到的_contianer 和 _regionManager的作用需讨论一下。如果接口并未定义他们,那么他们从何而来?我们是否需要将逻辑硬编码到模块中以找出这些依赖关系?
幸运的是,后一个问题的答案是“否”,此时,IoC容器就派上用场。加载此模块时,它从容器中被解析出来,同时会将所有指定的依赖关系注入到模块的构造容器中。
通过让模块直接访问容器,可以允许模块以一种强制性的方式在容器中注册和解析依赖关系。
要点2 :
CAL(Compoise Application Library)包含一个Region类,大体上,此类就是涵盖这些位置的一个句柄。Region类包含一个Views属性,它主要是在区域中进行显示的视图的只读集合。视图通过调用区域的add方法被添加到区域中。Views属性实际包含对象的泛型集合;它并非局限于仅包含UIElement。此集合将实现InotifyPropertyCollectionChanged,以使同区域相关联的UIElement能够与之绑定并观察其变更。
要点3:
为什么Region类的Views集合为弱类型而非UIElement类型。由于WPF可提供丰富的模板支持,因此您可以将模型直接添加到区域。然后,将会为该模型定义一个相关联的DataTemplate,它会定义模型的呈现方式。如果添加的项目是UIElement或者用户控件,者WPF会按原样呈现。这意味着为某个区域添加控件,只需添加相应的Model,然后定义一个自定义的DataTemplate即可控制显示,而不是创建一个自定义的OderView控件。
要点4:
区域的两种注册方式:
第一种是在XAML中通过使用附加属性的RegionName 来注释UIElement进行定义。如:定义MainToolbarRegion的XAML如下:
<ItemsControl Grid.Row="1" Grid.Column="1"
x:Name="MainToolbar"
cal:RegionManager.RegionName="MainToolbarRegion">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
通过XAML定义区域以后,他会在运行时自动注册RegionManager,这是由引导程序注册的一个符合服务。RegionManager实质是一个Dictionary,其中关键字为区域的名字,值是Iregion接口的实例。需要注意的是:如果使用附加属性不起作用,或者需要动态注册其他区域,您可以手动创建Region类或者派生类的实例,并将其添加到RegionManager的Regions的集合中。
第二种方法是:CreateRegion,它会返回新的AllActiveRegion实例。区域可包含处于活动状态或者非活动状态的视图。对于ItemsControl,他的所有项目始终都处于活动状态,因为它没有选择意识。但是对于其他类型的区域(如Selector),一次仅选择一项。视图可实现IactiveAware接口,以便能够从其他区域处得到也被选中的通知。只要视图被选中,他就会将其IsSelected属性设为true。
要点5:
适配器本身的实现方式完全取决于它所应用到的UIElement的类型。
要点 6:
在复合应用程序开发过程中,可能不得不创建一些附加的区域适配器和区域。例如需要用来适应第三方供应商提供的控件的适配器。要注册新的区域适配器,就需要覆盖引导程序中的ConfingureRegionAdaterMappins方法。
要点7:
本地作用区域
默认情况下,应用程序中只有一个RegionManager实例,因而使得每个区域的作用范围都是全局有效,这使用与很多情况。当有时可能希望每个视图都像外壳一样拥有自己的区域。CAL允许您为视图定义本地RegionManager,这样在其中或者其子视图中定义的任何区域都会在该本地区域中自动注册。
要点 8 :
WPF中的元素可绑定到命令以处理执行逻辑以及启用或者禁用元素。当UIElement被绑定到命令时,如果命令的CanExecute属性为false,则它会自动被禁用。在XAML中可通过声明的方式绑定命令,WPF预设即提供RoutedUICommand。
要点 9 :
CAL包括许多新命令(如DelegateCommand<T>,它允许您为构造函数中的Execute和CanExecute方法制定两个委派)。通过此命令,在连接各个视图时,您不必通过在视图本身定义的方法进行委派,也不必为每个操作分别创建自己的命令。
要点 10 :
CAL包括CompositeWPFEvent<Tpayload>类,它继承了EventBase类,并提供对WPF的特定支持。此类使用委派而非网站的.NET事件类执行发布。实质上,它使用默认起委派作用的DelegateReference类(有关弱委派的详细信息,请参阅 msdn.microsoft.com/library/ms404247)。这将允许对订阅者进行垃圾回收,及时他们没有显示取消订阅。
Silverlight about PRISM
要点1 :
MoudleInfo中指定一个Ref值,Ref值时包含模块的.xap文件的路径,在指定.xap文件时,引导程序知道程序集不可用,因此转到服务器,异步检索.xap文件。加载.xap文件后,Prism会加载程序集,创建模块类型并初始化模块。
对于包含多个模块的.xap文件,可以创建一个MoudleGroup,用于包含一组MoudleInfo对象,还可以设置MoudleGroup的Ref,以便从单个.xap文件加载所以的模块。
在创建.xap文件要包含的Silverlight模块时,需要创建一个Silverlight应用程序(而不是Silverlight库)。然后,需要引用要放在.xap文件中的所有模块项目。您需要删除app.xaml文件和page.xaml文件,因为该.xap文件不会进行加载,也不会像典型.xap文件那样运行。该.xap文件只是一个容器(可以是.zip文件)。
要点2:
CAL支持在XAML中直接定义区域,方法是对RegionManager类使用附加属性,通过此属性可以再Shell中指定区域,然后指示区域中应承载的视图。属性RegionName可以应用于ItemsControl及其派生控件(如ListBox)、Selector及其派生控件(如TabControl)、以及ContentControl及其派生控件(如ScrollViewer控件)。
要点3:
如果要使用MVVM模式定义视图,可以将容器的区域与服务位置相互混合,是视图和视图模型相互独立,从而模块可在运行时加入视图和视图模型。如:如果更改GameListMoudle,则可以向容器注册视图和视图模型,然后再将视图应用于区域之前加入视图和视图模型。如下说是:
通过此方法可以使用UI复合,同时保持MVVM的严格分离。
要点4:
通过UI复合在应用程序中使用多个视图后,将面临一个常见问题。即使已构建了独立视图来支持更好的测试和开放,通常仍然存在一些接触点,使得视图无法完全隔离。因为需要进行通信,这些接触点在逻辑上是耦合的,不管这些接触点在逻辑上如何耦合,您还是希望视图的耦合尽可能松散。
为实现松耦合和跨视图通信,CAL支持一种称为“事件聚合”的服务,通过该事件聚合,全局事件的发布者和使用者可以访问代码的不同部分。这类访问提供了一种非紧密耦合的直接通信方式,可使用CAL的IeventAggregator接口完成,使用该事件可以跨应用程序的不同模块发布和订阅事件。
在通信之前需要,创建一个从CompositePresentationEvent<T>类派生的简单事件,使用此泛型可以指定要发布的事件的负载。定义完事件后,事件聚合可以通过调用其GetEvent方法来发布事件。这会检索要聚合的单一事件,调用此方法的第一个事件聚合器和创建单一实例,可以从该事件调用Publish方法创建事件。作为订阅者,还可以指定在特定情况下调用的筛选器。
要点5:
在Silverlight中(与WPF不同),没有真正的命令基础结构,CAL支持一个类来帮助解决这一问题:DelegateCommand(在Silverlight没有支持此功能之前)。如要将此命令绑定到XAML中,请使用Microsoft.Practices.Composite.Presentation.Commands命名空间中的Click.Command附加属性。然后将此命令的值绑定到ViewMode中的命令。
要点 6 :
依赖关系可以在对象的构造的过程中注入到项目中,这也是依赖关系注入的工作原理。容器的任务就是处理类型的创建,通过容器,可以注册类型和解析所注册的类型。
例如:假定现在有一个IdataAcess接口的具体类型,在启动应用程序的过程中,可以指示容器注册该类型,在应用程序中需要该类型的其他任何位置,都可以要求容器解析该类型。
此外,容器还可以处理依赖关系的结构注入,如果某个需要由容器构造函数创建的对象接受容器可以解析的接口,则容器会解析该类型并将其传递给构造函数。
要点 7 :
向容器注册类型时,同时指示容器以特殊方式处理该类型的生存期。例如:如果要处理日记记录组件,则可能希望将该组件视为单一组件,这样需要日志记录的每个应用程序部件不会各种获得一个副本(这是默认行为)。为此,可以提供LifeTimeManager抽象类的实现。
支持多个生存期管理器:
ContainerControlledLifetimeManager 是按进程的单一实例,而 PerThreadLifetimeManager 是按线程的单一实例。对于 ExternallyControlledLifetimeManager,容器具有对该单一实例的弱引用。如果对象在外部发布,则容器会创建一个新实例,否则返回弱引用中包含的活动对象。
要点 8 :Prism的Command
P&P 小组也看到了WPF Command 的诸多限制,特别是与UI 元素耦合以及不支持命令组合,所以他们在Prism 中便增加了另外一套Command :DelegateCommand 和CompositeCommand 。
DelegateCommand :实现了WPF 的ICommand 接口,仍只支持一个CanExecute 和Execute 挂接,但其实现一个称为IActiveAware 的接口用于指示是否处于集合状态,非激活状态的DelegateCommand 始终得不到执行。
CompositeCommand :也是WPF 的ICommand 接口的一个实现,但其同时也是DelegateCommand 的组合,可以向其中注册或取消注册DelegateCommand ,当其中所有处于激活状态的内置DelegateCommand 都可以被执行时其CanExecute 才返回true 。当CompositeCommand 被执行时其内部的DelegateCommand 将被依次执行(如果可执行的话)
DelegateCommand 和CompositeCommand 的语法层面的东西这里就略过不讲了。
另外,DelegateCommand 和CompositeCommand 并不是RouteCommand 的替代品,而是强有力的补充,RouteCommand 的路由机制让我们不必关心目前用户焦点在哪里,况且,WPF 内置的数十种常用Command 为我们节约了不少开发时间。
要点问答:
1. 为什么要使用依赖注入容器
我们知道, 在Composite Application 中各个模块之间是松耦合的关系, 也就是在设计的时候尽可能地减少模块间的依赖, 但无论如何从业务角度讲, 他们之间总还是要相互通信与合作的。 所以依赖注入容器在这其中扮演了一个桥梁般的角色。比如当创建某一个组件实例的时候, 其依赖于另外的一个组件或服务, 此时依赖注入容器会将其需要的信息" 注入(Injection)" 进去, 采用注入的方式就避免了直接引用之间的耦合. 除此之外, 使用依赖注入容器还有以下几方面的好处:
组件(Component) 不必自己去定位其依赖项和维护依赖项的生命周期,替换组件的依赖项的具体实现时不会影响到组件代码,由于依赖项比较容易Mock, 所以组件变得更易测。
由于系统所需要的新的服务很容易被添加到容器中, 系统维护难度也降低了。
2. Prism 的依赖注入容器
打开Composite Application Library(CAL) 的源代码, 我们似乎没有找到一个依赖注入容器的实现, 而是找到了一个依赖注入容器外观接口IContainerFacade, 其利用了外观模式来高层抽象了一个依赖注入容器的最最基本的功能" 解析"(Resolve).
事实上Patterns&Practices 团队在设计时为了让Prism 兼容更多的依赖注入容器( 比如Spring.Net, Castle Windsor, Unity 等等) 而设计了这一接口, 所以在Prism 源代码中用到的都是IContainerFacade 接口而不是一个具体的容器.
但是, 在默认情况下,Prism 使用了另外一个开源项目" Unity" 中的轻量级的可扩展的依赖注入容器来作为默认容器UnityContainer. 该容器实现了构造函数注入、属性注入和方法注入三种注入方式。
3. 依赖注入容器的性能开销
由于将类型注册到依赖注入容器, 然后使用容器来解析(Resolve) 出类型实例对象时, 根本机制是" 反射"(Reflection), 所以开销是比较大的, 那么如果你需要频繁地创建大量的实例的话, 是否使用依赖注入容器是需要认真考虑的问题. 大量的依赖和很深的依赖嵌套也会导致性能问题. 另外, 如果一个组件既没有依赖其它组件或服务, 也不为其他组件所依赖, 那么将其放到依赖注入容器中也是不明智的.
4. 是否注册为单例
我们在向依赖注入容器注册默认类型映射或注册Instance 时涉及到一个问题是: 是否按照单例模式注册. 如果是的话, 那么每次都容器中解析出来的对象都是同一对象实例, 否则每次都是重新New 的一个实例. 一般说来, 对于一些全局服务以及共享状态等我们会将其注册为单例模式, 对于那些其被每次都需要拿一个新的实例去注入的依赖项我们将其按照非单例模式的形式注册.
5. 使用IUnityContainer 还是IContainerFacade
他们分别位于 Microsoft.Practices.Composite 和Microsoft.Practices.Unity 命名空间下, 虽然都可以作为容器的高层接口,但使用IUnityContainer 基本上就意味着直接使用Unity 容器(Unity 项目中的那个依赖注入容器), 而使用IContainerFacade 则意味着你可以兼容格式各样的容器. 但, 我们知道IContainerFacade 基本上只提供了一个功能" 解析", 但就一个基本的依赖注入容器而言, 往往不仅仅是这一个功能, 比如至少还要有类型注册、实例注册等等。所以在大多数情况下推荐使用IUnityContainer ,从开源项目"StockTraderRI” 中我们可以看到这一点, 除非是下面的情况之一:
l 你作为ISV( 独立软件供应商,independent software vendor) 编写一些提供多容器支持的服务
l 你的服务被用到多容器的系统中
当然在最新的版本4.1中已经有了新的容器MEF(Managed Extensibility Framework)可供选择。
6. 代码配置还是配置文件配置
关于这个问题, 我引用一下 Martin Fowler 的一段话为大家提供一些参考:
“代码配置 vs. 配置文件另一个问题相对独立,但也经常与其他问题牵涉在一起:如何配置服务的组装,通过配置文件还是直接编码组装?对于大多数需要在多处部署的应用程序来说,一个单独的配置文件会更合适。配置文件几乎都是XML 文件,XML 也的确很适合这一用途。不过,有些时候直接在程序代码中实现装配会更简单。譬如一个简单的应用程序,也没有很多部署上的变化,这时用几句代码来配置就比XML 文件要清晰得多。
与之相对的,有时应用程序的组装非常复杂,涉及大量的条件步骤。一旦编程语言中的配置逻辑开始变得复杂,你就应该用一种合适的语言来描述配置信息,使程序逻辑变得更清晰。然后,你可以编写一个构造器(builder )类来完成装配工作。如果使用构造器的情景不止一种,你可以提供多个构造器类,然后通过一个简单的配置文件在它们之间选择。
我常常发现,人们太急于定义配置文件。编程语言通常会提供简捷而强大的配置管理机制,现代编程语言也可以将程序编译成小的模块,并将其插入大型系统中。如果编译过程会很费力,脚本语言也可以在这方面提供帮助。通常认为,配置文件不应该用编程语言来编写,因为它们需要能够被不懂编程的系统管理人员编辑。但是,这种情况出现的几率有多大呢?我们真的希望不懂编程的系统管理人员来改变一个复杂的服务器端应用程序的事务隔离等级吗?只有在非常简单的时候,非编程语言的配置文件才有最好的效果。如果配置信息开始变得复杂,就应该考虑选择一种合适的编程语言来编写配置文件”。
自己实践Prism(Comprise Application Guidance for WPF)遇到的问题:
问题 1 :重写启动类UnityBootstrapper时遇到Resolve类型不匹配问题。
描述:在添加了命名空间:
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.UnityExtensions;
以后(无异常)在重写protected override DependencyObject CreateShell()
时遇到:
函数Reslove类型不匹配问题。再接办法添加对命名空间:
using Microsoft.Practices.Unity;的引用。
原因:该函数的重载版本是在这个命名空间下,定义的。