OEA框架的核心之一是AutoUI,其职责是面向领域模型及UI元模型进行生成统一的界面。
在本次的迭代开发中,需要对命令按钮的生成方式进行一些定制。由于原来并没有为这样的需求留有特别的扩展点,加之原来的生成代码是过程式的代码、且也变得比较冗长,所以我们决定对这一部分的代码进行重构。
原来的模式
历史代码中,为某一实体类生成命令按钮的流程是这样的:
- 找到实体类可用的所有命令按钮元数据。
- 对它们进行过滤,依靠权限、版本的客户化元信息等。
- 构造几个生成控件的List容器,分别是:itemsInToolbar,itemsInContextMenu,itemsInGroup。
- 遍历所有的命令按钮,根据其对应的元数据,分别生成相应的控件(按钮、菜单等),然后添加到容器中。
其中,还有对某些命令的特殊生成处理。例如,为了给命令生成一个附带的文本框,特别添加了IParameterizedCommand接口,实现这个接口的命令,则会调用它自己的控件生成方法来生成控件。应用开发时,扩展的命令需要实现自己的控件生成方案。 - 对同一容器中的命令控件进行排序和一些其它的操作。
- 把容器中的每一项添加到界面中。
由于功能是一点一点加进来的,整个代码是过程化的,冗长而不易维护。扩展起来也比较不便。原来只能实现IParameterizedCommand接口并自行生成文本控件,要在总体上控制整个生成流程也只能修改上面的流程中对应的代码,最终只会导致代码膨胀得无法维护。
草稿
重构不是重做。所以我们不是全部推翻重做,而只是把流程进行优化,并进行职责划分,用适当的对象来承担对应的职责,让类与类之间的协作来完成整个流程。
首先,整个流程中比较重要的是控件的生成和分组。在进行OO设计时,先要对这部分进行抽象。先看看原来的生成的ToolBar的一张图:
(样式没有做,不好看,哈哈。)图中,主要有三类:分组下拉、带文本的按钮、一般的按钮,当然还应该包含右键菜单中的菜单项。再加上这些迭代的新的样式,画出了下面的草稿:
图中,主要的GroupGenerator的职责是对一组命令进行控件生成,一般情况下一组命令可以生成SplitButtons、GroupTextbox。当一个组中只有一个命令时,它就变为了特殊的ItemGenerator,当个命令可以生成:文本按钮、一般按钮、菜单等。而如何把所有的命令进行分组并生成GroupGenerator,就是GroupingAlgorithm的职责。
详细设计
(以下内容中涉及具体的OEA的类的职责,用于项目组内沟通,不关心的朋友可以直接跳过细节描述。)
最后的具体设计方案中,分为以下几下主要的部分:
- CommandAutoUIContext:这是对整个生成环境的抽象。
- CommandAutoUI:这里面包含了整个生成的流程。
- Generators:这里面包含了所有的框架内置的命令组生成方案。
- GroupAlgorithms:这里用于把命令进行分组,并为命令组分配生成器。
以下,详细说明每个部分的设计:
CommandAutoUIContext:
CommandAutoUIContext 表示生成流程的上下文对象。它比较简单,只是包含了整个生成流程中需要用到的参数,这些参数包含:需要生成命令的实体的元数据信息、可用的ToolBar对象、可用的菜单、所有实体包含的命令、命令要用到的参数。
CommandAutoUIComponent 类表示整个生成流程中的可用的组件,这些组件都可以直接获取上下文对象中的内容。
CommandAutoUI
这里包含整个生成的流程所用到的核心对象:
CommandGroup 表示一个命令组,其中有组名。
GroupGenerator 是一个命令组的生成器,这里为它分配了以下职责:为命令组生成控件、把控件添加到上下文中。
GeneratableGroup 整合了上述两个对象,表示一个可生我生成的命令组。
GroupAlgorithm 表示某个命令的抽象的分组算法。注意,它只负责对某个命令进行分组。
GroupOperation 作为分组操作的执行者,调用分组算法对所有命令进行分组。它负责对所有的命令进行分组。
以上对象作为生成流程的核心对象,被CommandAutoUIManager进行组织并完成最终的界面生成:
GroupGenerators
图中列出了框架内置的可能用到的所有生成器。当然了,要扩展界面生成时,只需要编写新的子类就行了。
具体的内容在前面的“草稿”已经有所描述,在此不再赘述。
GroupAlgorithms
GroupAlgorithm 是策略模式的应用。
框架中内置三个分组算法:
DropDownListAlgorithm 表示把某一个命令分组到下拉组中。
DefaultAlgorithm 表示默认的一个命令一组的分组算法。
GenericItemAlgorithm 表示指定新的生成方式的分组算法。(添加这个类,是因为扩展时,80%以上情况只是为命令更换一种生成显示界面而已。)
整个流程用对象描述如下:
其它相关改动
IParametrizedCommand 由原来的只支持 String 的 附加文本框命令变为更抽象的“需要参数的命令”。界面生成的控件在用户“填入”数据时,会组装出其所需要的参数,然后对它进行回调:
1
2
3
4
5
6
7
8
9
10
11
|
/// <summary>
/// 一种需要其它参数才能执行的命令。
/// </summary>
public
interface
IParametrizedCommand : ICommand
{
/// <summary>
/// 界面中输入的参数改变时,会通知这个Command参数值改变了。
/// </summary>
/// <param name="value"></param>
void
NotifyParameterChanged(
object
value);
}
|
总结
这里再总结一个小的经验:
在开发过程中,为了简化代码,曾尝试使用设置属性的方式来设计构造函数的必要参数。但是属性设置并不是必需的,一旦忘记,则会需要调试才能找到问题所在,得不偿失。也就是说:
- 不要以为类少就认为自己能记住其中的设置约定。最好是让编译器提醒你。 :)
- 不要因为简化代码而去尝试违反一些好的设计规范。否则,不但可能写出不易调试的代码,而且影响后人的阅读。