ExtFrame里的核心设计模式之一
其实这个框架里用到的模式很多,例如持久层创建数据库连接使用的Factory,而我最喜欢用的是Singleton,例如配置文件处理类,逻辑Handler类等大多都采用了延迟加载的Singleton模式,不过这些都不是这个框架设计的核心模式
当初设计这个框架时就确定了两大核心模式:Command和TemplateMethod,另有一个配合的是使用IOC机制加载对象处理器,这个应该不算设计模式了
本篇就重点介绍框架里是为何以Command为主以及如何应用Command模式设计整个架构的
说起来,这个要起源于以前做的一个C/S的项目,那个项目里大量采用了Command模式并且应用到了树上,也就是当用户点开一颗树时,任意一个节点上右键点击时,会弹出一个浮动菜单,而这个菜单的内容是根据节点的类型和位置配置的,例如活动项目树下的任务下的文件夹配置了一个菜单项,这个浮动菜单的内容和归档项目树下的任务下的文件夹的菜单就可能是完全不同的,但有部分菜单的功能可能是相同的,这些菜单项就都是使用Command模式配置运行的,点击菜单时运行对应的Command命令,而每个节点显示什么样的菜单只要配置下就可以了,无需写上一大堆的创建或执行操作的命令
后来做B/S项目时,因为大家都不熟,基本都是使用封装后台控件的模式(扩展.Net里的控件使这些控件可以自动加载数据),然后使用Ajax机制又写的乱七八糟,做了个基于ashx的架构,嗯,我其实没参与,但是我对这个架构足以表达深刻的痛恨:太难维护了,前台又是JQuery又是ExtJS(只用了部分控件UI来美化页面),脚本一层套一层(我基本是看不懂这些脚本的,因为原本我就不怎么懂JavaScript),ashx层也是继承的莫名其妙,然后是HttpHandler层,乱七八糟的跳转......总之,稀奇古怪的BUG层出不穷,架构里天才的想法也一堆,制造出N多的问题
记忆里最有意思的一个BUG是某程序员采用了把表格的HTML放置到xml配置文件里做为一个节存在,使用Template模式载入表格,然后使用CSS控制表格里不显示的部分(例如一组按钮,是否显示基于对应的逻辑运算给出的CSS结果),这个架构他称之为Skin,嗯,好吧,我承认,要修改Skin的时候确实是比较容易,可是用户那里出了BUG时,要死了,Skin里写的按钮的脚本,里面带了引号,在IE里出错,后来就在后台检查把引号编码,好吧,IE解决,FF又出错了,换种方法,FF解决了,IE又不行了,我倒......虽说用户不使用FF,但是不能让我没法调试是不.......
扯远了,不过,这个BUG和我这个框架的前面一版还是有些关系,涉及到Grid的功能实现,这个问题在Grid章节再说了
继续说Command,重新设计.Net应用开发框架时(确切的说是前面未完全采用ExtJS做为系统布局的一版时),我就考虑把系统的所有功能都肢解,然后使用Command模式进行封装,这样当初考虑的最大好处就是容易维护,所有的代码都比较清晰,例如保存的就是在保存的Command里,第一版后来就实现了这个功能,但是后来整个系统因为项目原因马马虎虎的实现了基础设计的那些功能就结束了(也就是CRUD等功能),再后来重新考虑制作现在这个框架时,Command模式的定位就比较清晰了:系统脚本调用及Ajax调用的核心实现模式,因为这个框架设计的几乎所有功能都是以Ajax模式调用数据,都完全没有页面刷新的问题了,所以Command模式毫无疑问的成为了核心模式之一
好吧,下面说的就是系统是如何设计Command模式的
首先,我把所有的操作全部分组,然后放在配置文件里(就叫Command.config),一组操作是什么呢?就是一个页面上显示的功能集合
例如,添加用户的页面,包括了保存和关闭的两个按钮(这个最简单不是)
那是这么写的
<CommandList name="adduser" moniker="UserInfo" description="保存用户数据"> <Command label="保存" image="icon-save" command="FormHandler.doAdd(window_add_user);" privilege="1" /> <Command label="关闭" image="icon-close" command="window_add_user.hide();" privilege="2" /> </CommandList>
看到了不,就两个按钮,那个privilege就不解释了,涉及到系统的权限模型,这个东西不准备放出来
添加用户的界面时这样的:
在这个页面里,有一个Toolbar条,创建这个页面或第一次载入这个页面时会执行如下代码:
show: function() { if (!this.ready) { CommonButtonBuilder.buildToolbar(adduserbar, 'adduser'); this.ready = true; } },
这样就动态的创建了定义在配置文件里的按钮,一个点击时执行了关闭这个窗口的命令,另一个执行时则调用了封装的新增数据的命令
其实这个功能一开始设计时没有采用Ext.Toolbar,而是想创建在Ext.Window或Window的Form里显示在窗口的下方,但就为这个问题折腾了好长一段时间差点没做出来(当需要刷新重新创建按钮时会有问题),后来就统一采用了Toolbar模式
这个只是最初步的设计,看起来很简单,或许有很多人觉得没必要:直接在Window里使用Buttons[],可以很容易的写出同样的功能么
啊,我先说一个优点:咱这个好维护不是,如果有问题不需要改脚本代码,改改配置文件就可以了,而且不同的窗口的Button,都可以直接拷贝过去改改参数就好了
那么继续往下看,看这个复用,你就体会到了这个设计为什么会成为架构的核心了:
这是啥?是角色管理的界面,这个界面里有一个按钮条,外加一个GridPanel
看到复用了么?上面的按钮条?不,还有一个:操作列
没错,这也是一组Command,专门定义给这个Grid使用的(啊,那个分配用户和设置权限不是的,虽然看起来也很象)
<CommandList name="rolegrid" moniker="Role" description="角色信息"> <Command label="编辑" image="icon-edit" command="FormHandler.showData(window_edit_role, '{id}');" /> <Command label="删除" image="icon-delete" command="rolegrid.doDelete('{id}');" /> </CommandList>
还带参数的,看见了没......
这个复用就显示出重要性了吧?所有的Grid,都只需要指定一个操作列的对象名,就可以自动生成了
还有另一个复用:工作流,去参见工作流简介的那章,这里就不说了
还有其它优点么?当然,不会这么简单的
举两个例子吧
1.如果不同的人进入同一个窗口,他们能看到的操作功能是不一样的,要怎么解决?
2.如果一个Grid里的操作,每一行根据当前数据的状态显示不同的操作,要怎么解决?
我相信以传统的方法,一般都需要写很多很多的脚本,在前台或后台进行很多的判断,再来设置按钮是否显示,或者设置操作的CSS样式什么的
在我的框架里,No,处理方式非常简单,在CommandList的属性里加一个节:filter="xxxfilter",使用一个过滤器进行过滤就可以了
例如工作流章节里见到的:
<CommandList name="carrequestflow" moniker="CarRequest" description="用车申请流程" filter="flowcommandfilter"> <Command key="pass" label="通过" image="icon-pass" command="Flow.Pass(window_flow_carrequest, 'window_flow_carrequest.hide();');" /> <Command key="veto" label="否决" image="icon-veto" command="Flow.Veto(window_flow_carrequest, 'window_flow_carrequest.hide();');" /> <Command label="关闭" image="icon-close" command="window_flow_carrequest.hide();" /> </CommandList>
加载了一个专门用于流程过滤的过滤器
框架里最常见的过滤器,是权限过滤器,就是根据当前配置的这个Command的privilegeID自动调用权限模块去进行过滤,如果用户没有被授予当前操作的权限,该按钮就不会被返回到前台去
1和2的情况如果即有权限控制,又有业务逻辑控制,那么一般要在逻辑控制层里写的很复杂了
而我这里怎么处理?把两个过滤器串起来用逗号隔开就好了,这些过滤器一般都是系统可用的,配置下就可以了,除非遇到了比较特殊的业务逻辑,那么才需要针对这个业务逻辑再去补个过滤器就好了
这个过滤器的写法,涉及到系统的IOC模式和另一个核心的设计模式:TemplateMethod,这章就不说了
考虑到这个版权什么的,我这里只解释了架构,关键的代码很多都不放出来,其实实现起来也不难的
如果有谁考虑应用我这个架构的设计,请在设计文档里注明参考的地址哦