最近正在做一个项目,把其中的一些设计思路和大家共享,希望大家批评指正。
业务背景:这是一个给某企业某部门开发的一个售前产品的报价系统。一些情况如下:
1)虽然有签字的需求文档,但用户不能明确确定他们想要的,他们明确告诉我们的是,系统是一定要变的,小变一定,可能大变。
2)系统的产品种类比较多,而且每种产品都有不同的信息,维护的方式有差异。
3)用户希望把这个项目做成以后开发的模版.
设计目标:
能够通过清晰的修改来应对变化。系统并不一定做到很灵活,但要做到修改程序的时候,思路明确,不会牵一发而动全身。我们认为用户的修改可以分为几个层次:a.界面变化;b.数据字段的增加、修改;c.局部业务逻辑发生变化;d.业务流程发生变化。对于这些不同层次的修改,可以有不同方法,但要保证一致性。
设计思路:
对于不同的变化采用不同策略:
a.界面变化。解决方法:界面/对象映射层
b. 数据字段的增加、修改。解决方法:NHibernate,界面/对象映射层
c.局部业务逻辑发生变化。解决方法:页面控件校验映射,页面控件校验映射
d.业务流程发生变化。本项目没有相关的设计,可能只有工作流可以解决这个问题了。
具体方式:
1)O/R maping采用NHibernate。这个大家很熟悉了,本文略。
2)写一个界面/对象映射层。我们使用一个XML文件来表达如何把对象里的数据显示到界面。见下面的内容。这个配置文件并不处理控件的显示和位置,因为这是HTML的优势,系统只关心数据的绑定。
<?xml version="1.0" encoding="utf-8" ?>
<map>
<class caption="BaseCustomer" classname="SQS.BusinessRules.Data.BaseCustomer">
<property controlid="txtProductGroupCode" buildclass="Map.PraseTextBox" value="GroupCode" />
<property controlid="txtProductGroupName" buildclass="Map.PraseTextBox" value="GroupName"
format="" />
<property controlid="ddlProductTypeID" buildclass="Map.PraseDropDownList"
value="ProductTypeID" />
<property controlid="cbStatus" buildclass="Map.PraseCheckBox" value="Status" />
<property controlid="txtTarget2" buildclass="Map.PraseTextBox" subvalue="Target2"
value="TargetInformation" />
<property controlid="txtTarget3" buildclass="Map.PraseTextBox" subvalue="Target3"
value="TargetInformation" />
</class>
</map>
说明:
class.caption:要绑定的对象关键字
class.classname:对象的类型
property .controlid:页面控件ID
property .buildclass:控件绑定的解析器
property .value:绑定的对象的属性名称
property .format:比如对于日期你可以写成yyyy-MM-dd
property .subvalue:绑定的对象的子属性名称(在后面有说明)
以往的开发中,对象到界面往往需要花费大量代码,虽然可以用工具生成,但一些细节问题花费很大精力。比如:某表中一个字段的类型是日期,且某一行该字段值为null,(如无处理)到对象后将会变成默认值日期最小值,即时什么也没改保存的话,该行数据变成了这个最小日期。这样数据并处于不对称状态,以往在这方面,包含了大量的重复代码,采用这个结构以后,这种问题便不复存在。
再举一个例子说明采用映射的好处,比如:如果防止SQL注入,有三种方式:a.写一套控件,b.在界面来判断,c.在数据库保证(比如用存储过程保存)。第一种方法比较花时间,而且如果采用了第三方的控件就不好办了。第二种方法,重复代码多,罗嗦。第三种方法效果比较好,但要求一定写存储过程或触发器,不太通用。
另外呢,应对数据表字段的增加这种变化,我们写了这样一种映射(见下面)
<property controlid="txtTarget2" buildclass="Map.PraseTextBox" subvalue="Target2"
value="TargetInformation" />
<property controlid="txtTarget3" buildclass="Map.PraseTextBox" subvalue="Target3"
value="TargetInformation" /> <property controlid="txtTarget2" buildclass="Map.PraseTextBox" subvalue="Target2"
value="TargetInformation" />
<property controlid="txtTarget3" buildclass="Map.PraseTextBox" subvalue="Target3"
value="TargetInformation" />
大家不知是否注意到这两个映射中的value是相同的(都是TargetInformation),也就是说他们绑定到对象的同一个属性,那又何意义?请看在数据库中的存储的格式,大家就明白了:
大家可以看到,在数据库中,他们被存到了一个字段里,我们认为,如果用户增加的字段仅仅用于描述的话,这种方案还不错。或许您会说,这样在查询里面,如果想要查询某些字段是会很慢的,我门认为解决办法是:写一个触发器,在触发器中,分解字段(上面的TargetInformation),把相关的字段(比如Product3)存储到一个实际的字段(Product3)中去。这是一个混合的方法,面向对象和面向数据的结合。