正在做一个系统,是在linux下面使用Kylix完成CGI。功能是和学校业务相关的。其中,我负责了技术方面的设计。
简单说说我们的解决方法,请 指点一二...
还要说明:一些业务层,业务对象层等概念我并不是十分的清楚。下面虽然也会提到这些概念,但实际的所指可能不同甚至是
错误请大家注意,不要受我的误导.不然我的罪过就大了
请大家随我一起经历一次我们的项目过程吧:.....
我们同样规定:
1, 任何人不能直接访问数据库,所有对数据库的访问【执行SQL语句】必须通过组建完成
2, 要求技能型面向对象的封装。考虑到不同的员工对OOD思想的理解深浅不一,所以我们规定只要做到实体和控制相分离就可以。
但在项目进行中就变成了只要求公用组建符合这项规定就行了。
我们的面向对象设计很快的完成了。看起来像这样:
tStudentManager = calss
addstudent(name : String;
age : Int......);
delstudent(name : String);
overload;
delstudent() overload;
.....
end;
tstudent = class
name : String
age : int;
function getnamebyID(ID : Int) : String;
...
end;
刘润东的那本UML对象设计与编程【向大家推荐这本书,虽然有些名词写的不太标准,但哟些咚咚很是实用】
还有那本版更经典的著作《名字忘了 hehe :(》。都有一些这方面的论述。
书中讲到:实体的创建、销毁都要由控制类来实现。所以所有使用这个控制类的程序如果要创建一个学生实体,都必须
把学生所有的属性值当作参数传给控制类,然后控制类负责创建学生实体。
第一个问题就来了:学生的属性实在太多,有40个左右。如果把40个属性都当作参数传递过去......?????
为了解决这个问题,我们又为学生创建了一个record 里面又学生所有的属性。传递参数时,把这个record的指针传过去。
这个想法时参照JAVA的Value Object来得,也不知道我理解的Value Object对不对?
这样控制类变成了:
tStudentManager = calss
addstudent(&studentrecord);
getstudentbyid(id : Integer) : TStudent;
.....
end;
这样程序必须先创建一个学生实体,必须先生命一个学生的record。
虽然解决了参数太多,但是又来了一个疑问:我有了学生实体,为什么还要创建这个record?有必要吗?
解决了这个问题,项目得以继续。
很快,我们的公用组建部分开发完成。主要有
session,log, SQLDriver
//因为我们只能使用Apache,不能使用JAVA的东西。而kylix提供的session处理不能满足我们的要求,
//只好自己做了
羡慕死做JAVA的了
SQLDriver主要负责执行SQL语句,并返回一个结果集【dataset】
大家开始完成业务模块。这时开始感觉到大家的水平参差不齐,导致业务模块之间的衔接有一些隐含问题。为了保险
我们决定在业务模块不再使用面向对象的开发技术。而是大家最熟悉的直接完成CGI。但规定:所有的数据库访问必须铜鼓SQLDriver
。已经又公用组件的必须使用公用组建;不允许访问别人的数据表。
自然,学生时最长用的实体对象。这是第二个问题来了:
由于学生的属性众多,对他的查询方式也就最多。各种各样的查询很快提出来了。
开始我们没有意识到这个问题,只是简单的规定:负责公用组件的人负责为大家的每一中查询要求完成一个函数接口。
但随着越来越多的查询方式【后来更多的是其他的修改, 各种条件的删除】。我们的学生控制类必须要实现数百个函数了。
负责学生组建的人差点从二楼跳下去.....hehe
如果允许直接构造SQL语句访问数据表,那么我们前面做的工作就白做了
只好想别的方法解决。
找来找去,Delphi中没有发现十分好的解决方案【或许是我不知道】
又去JAVA中找。发现了O/R Mapping。不过不太好实现。而且我们对这个O/R Mapping理解也不深。
所以我们根据思路【实际上是根据别人实现的一段代码】做了一些设计修改。
TSQLManager = calss
addtiaojian(recordname, option, value : String);
getSQL : String;
end;
TStudentSQLManager = calss (TSQLManager )
//主要是载创建类的时候把某一个实体类和数据表重的关系映射添加进来
end;
tStudentManager = calss
private
SQLManager : TStudentSQLManager ;
public
addtiaojian(guanxi, recordname, option, value : String);
addstudent(&studentrecord);
getstudentbyid(id : Integer) : TStudent;
.....
end;
---------- -------------------- ---------------
| | | | | |
|record |-----|StudentSQLManager |----|Data table |
| | | | | |
|--------| |------------------| |-------------|
^
|
|
-----------------
| |
|StudentManager |
| |
| |
| |
|---------------|
上面,StudentManager使用SQLManager来处理生成SQL语句。事实上,StudentSQLManager知道record 和具体数据表字段的对应
关系。属于StudentManager的私有类。没有违反我们前面说得规定。
前面在解决第一个问题时,我们为学生创建了一个record。里面又学生所有的属性。实际上,这些属性肯定和数据表中的
某一个字段相关联。而这个关联关系就由SQLManager来维护。
SQLManager是一个抽象出来的类。主要实现的功能是根据条件生成相应的SQL语句。内部有一个record和fields的对应关系表。
外面的程序使用addtiaojian方法来添加SQL语句的where字句部分,当然也有其他的函数来添加 select部分,Order by部分。
外面的程序都是使用record中的名字来访问的。
比如record中有这样的定义:
studentrecord = record
name, address : String;
end;
数据表中这样存储
field_name varchar[40] not null default ''
field_address varchar[100] default ''
在 TStudentSQLManager 的构造函数中,添加上映射关系。然后业务模块的程序就可以这样使用了:
studentmanager.addtiaojian('and', 'name', '=', '张三');
studentmanager.addtiaojian('and', 'address', 'like', '%海淀%');
studentmanager.search();
就可以得到一个对象数组了。
在调用search时,studentmanager调用getSQL方法,根据条件列表生成SQL语句:
select * from tbl_student where field_name = '张三' and address like '%海淀%';
这样,业务模块仍然是不知道数据表结构,只知道record内容。而后台实际上可以根据需要把一个实体对应到多个数据表中;
因为这样做会增加不少的代码量,为了保证进度,我们规定:只有实体属性较多【5个以上,含5个】才需要继承TSQLManager添加
对应关系,在控制类中构造SQL语句。其他的只要求作出getNamebyID之类的接口就行了。
看了诸位的方法,感觉“人在昆明”的方法和我们用的很相似。不过在具体问题时,我们有一些不同,但思想是一致的。
我们之所以把实体和控制相分离最初是为了一个很简单的目的:不要直接访问数据表【最好能够做到数据表的无关性】。
更不要去访问别人的数据表。所以我们把数据表封装成一些控制类和实体。所有对实体的访问都必须通过控制类来完成。
类似把业务模块堪称显示层, 我们实现了一个MVC模式。
这样,Delphi的那些数据感知控件就用不上了。
好在我们是在linux下做CGI,不需要那些东东。如果是一个CS结构的程序,
我们八成是要用了。因为这样的开发效率高。而且对程序员的要求也不太高。只要能够保证开发效率和质量。是不是采用面向对象
并不是很重要。我们之所以定下如此的规定是因为我们在前面项目中的教训。同时我们也是为了以后开发效率和质量的提高,建立
一个寒夜那的开发模型。hehe 现在看这个目标可太大了!教各位耻笑了。
不过也是一条教训:在实施面向对象的开发方法时,不要一步迈的太大。
我得理论知识不足,希望没有犯什么概念错误。乱七八糟说了这么多,供大家拍砖。如果能够引起更多的讨论,那是最好。
要不大家指点以下我们的方案中不足之处,我就更感激不尽了...