很高兴又一次开始谈软件的架构了,不过这个的探讨与09年初写的浅谈MIS系统架构不一样,之前是理论,现在是实践,而且这次在实际项目中把之前的理论都实现了,有过之而无不及,验证和权限、各层之间的低耦合、不再需要托控件,等等都实现了,其实一切能够实现得益于这次架构的魂——一切数据都会经过架构的程序(我们有时叫底层,下面统一叫软件框架)。遗憾的是这次仍然是一个WinForm项目,如果是web开发那么其中的很多细节问题都需要从新思考,毕竟运行机制不同嘛。不过这个软件是一个非常成功的软件,而且实现了多语言、多单位和多币种等。
上面所说的各层之间的耦合、不再需要托控件等(其实还有很多)这些挺表面的东西可能大家并不以为然,那从架构的角度来看下这个问题。
1.这是一个什么样的架构,为什么要这样架构
之前看到高焕堂大师的《Android应用框架原理与程序开发》这本书,书的第二章《應用框架魅力的泉源:反向溝通》,当我第一眼看到“反向沟通”这个词,我感到作者是我的知音,当然,当我看完这章后我就成了作者的知音,因为作者把什么是“反向沟通”和为什么要“反向沟通”这个原理表达的很精准。
我常思考一个问题,那就是所谓的“三层架构”或者“多层架构”中如果实现表示层和业务层之间的低耦合,如果任由开发人员自己托控件,那么软件框架很难控制到它,虽然可以获得它,但在什么时候获得和这个控件是什么作用等都是软件框架无法决定的,无论是winform还是webform都是事件驱动式运行,开发人员会使用控件的那些事件和事件方法里写什么都是未知的,这样就可能会出现大量的代码冗余和层与层之间的高耦合,还有维护很不方便,同时这样也是一件浪费时间的工作。
要想解决这些问题,那首先就得让软件框架去完成大部分工作,当然是与实际业务无关的工作。如果我们有一个保存按钮,我希望当点击这个保存按钮时软件框架自动调用业务对象中的保存方法,而不需要实际业务窗体去添加事件和写调用的方法,这就出现了两个问题,一个是软件框架如何知道当前需要调用哪个业务对象中的保存方法,这个很简单,只要业务对象都有一个基类,然后实现各接口(如:ISavable),软件框架自己去调用相应接口方法;还有一个问题是要保存的数据在哪里?看来软件框架需要保持有数据实体的引用才行。
正如前面所说:一切数据(广义上的数据)都会经过软件框架。所以我的做法是:把需要用到对象都在软件框架的相应类里声明,像业务对象会在表示层的基类里声明,每一个窗体都有数据,这个数据也在表示层的基类里声明,等等(事实上在软件框架里声明的各属性很多,总共有十几个吧),开发人员需要给表示层基类的业务对象这个属性赋值,而数据则不需要去赋值,软件框架会自己去调用业务对象属性的查询数据的接口方法获得数据。
这样一来软件框架中还剩一个问题就是窗体上的控件如何在软件框架中声明?因为只有软件框架保持对窗体上控件的引用才可以用统一的方法进行数据绑定、可见和可用权限、验证以及它们所需要的事件,这就需要软件框架自己去创建控件,至于实际业务窗体显示哪些控件是由实际业务窗体向软件框架定制的(这个定制比较复杂,我把这块单独抽出来作为一个业务外观层,通过它能定制控件以及控件行为,后面会详细说它)。
可见:不再托控件不仅仅是目的还是实现这种架构思路的手段。
正因为一切数据(广义上的数据)都会经过软件框架,软件框架才能控制一切,“反向沟通”才能好好的工作。
2.基本原理
这里我将软件中会出现的窗体归了个类,每一种负责一种布局(窗体只负责布局,内部的控件等等由业务外观层去完成),比如带树的列表窗体(就是左边会显示一个树列表,右边一个Gridview的列表,树列表中的节点改变,Gridview列表的数据发生改变),这些窗体都继承自一个包含业务外观属性和一些方法的常用窗体类。实际业务窗体根据它的业务需要继承某一种布局的窗体,它还要使用业务外观层中的类创建窗体各个布局区域中的内容(很多控件),并给这些内容指定一个必选属性“字段名”(业务外观运行时会用这个“字段名”去绑定数据,并可以用这个“字段名”去索引到这个控件)和其他一些可选属性用来定制控件的内容、行为和样式等。
这样一来,业务外观层是重中之重,它负责界面的绘制、绝大部分交互的调度以及几乎所有的非功能需求(比如:刷新后焦点行定位问题)。下面是个草图,业务外观层简单的画了下(因为下一篇会着重讲它),为了简单说明问题,也只放了一个用于业务的接口ISelectable,事实上是有很多的,比如保存等,业务外观层依赖于所有的这些接口以完成界面的操作,而开发人员在写实际业务窗体代码中不需要主动调用业务对象或者接口。
如果换个角度去看,业务外观层不仅仅是上述作用,它是对平台(.NET)和组件的一层抽象,以另一种方式让开发人员编程,它还有它自己的运行机制。它还抛弃了以往拖拖拽拽操作麻烦、修改维护不方便(需要在设计器中设置,而现在可能只是移动上下行代码)、窗体风格不统一,同时它还屏蔽了第三方组件的使用(如果项目中用了第三方组件,但团队中不一定是每个人都很熟练的使用这套组件,那么这个方案能给大家节省很多时间)。
3.总结
常用窗体为各种布局的窗体的基窗体,它声明了业务外观属性,子类可以访问业务外观属性并设置自己的外观。
从常用窗体派生的窗体只负责布局,布局中的每一块称为区域,区域中的所有控件都是由业务外观层根据业务外观属性创建的。
业务外观层根据实际窗体中定制的操作创建事件和绑定对应的事件方法完成与业务对象的交互。
与业务对象的交互都是使用接口完成的,一个业务对象实现了某个接口就表示它拥有这样的能力,比如ISavable接口,就表示这个业务对象是可以保存的,所以我们定义了很多接口。
下一篇讲解释业务外观层的设计和权限与验证的设计。