本框架是针对数据库应用软件的。顾名思义,数据库应用软件是围绕数据库展开的,对数据库的操作,无非是查询、增加、删除、修改、保存,分别对应SQL中的Select、Insert、Delete、Update、Commit。数据库应用软件从本质上来说,就是将用可视化的界面让普通用户去执行这些SQL,当然,这些SQL不是随便就可以执行的,要遵循一定的规则,这就是业务逻辑。
所以,界面、业务逻辑、数据库构成了数据库应用程序。怎样去组织这三者就是本文要讨论的问题。
模式是解决一个特定问题的最佳方案,所以我们的任务就是设计出一个组织界面、业务逻辑、数据库的模式。
提起模式,自然会想起大名鼎鼎MVC,MVC是组织模型、视图、控制器的最佳方案,而我们的任务中,也是三个核心类型,能否直接用MVC解决呢?
显然界面对应视图,数据库对应模型,那么业务是否也与控制器对应呢?控制器决定界面对用户输入的响应方式,显然业务逻辑并不能与控制器对应。但在我们的框架中引入MVC显然也是一个好方案,我们可以另外引入控制器,但现在的问题是如何处理业务?
将业务放到界面中显然不是好主意,在此就不讨论了。那么能不能将业务放到实体里面呢?在讨论这个问题前先要对实体做一个定义。
在一些书中,实体定义为对应表中的一行数据,可是我们经常要同时处理多条数据,如主从表中,一个主表中的行往往对应从表中的多行,按前面的定义,做一个涉及主从表的业务可能涉及多个从表实体,这样是不是值得呢?
根据我的实践经验,把定义换一个字就可以了,实体定义为对应表中的一组数据。按照这样的定义,一个主表中的行对应的从表中的多行可以放到一个从表实体中去。
我们再回到前面的问题,业务是不是要放到实体里?回忆我做的项目,数据库结构一旦确定后很少修改,但业务逻辑却经常变化,一般情况下,所谓的需求变更都是业务逻辑的变更。根据“封装变化”的原则,业务与实体应该分开。
至此,我们的框架中有了四个核心类型:界面、控制器、实体、业务逻辑。我们还需要引入最后一个类型,即实体工厂。
在面向对象的程序中,对象的创建和销毁其实是很麻烦的一件事,也非常消耗资源。其中最典型的就是实体,一般实体创建时需要从数据库中取数据填充到自己的实例变量中,销毁时需要销毁这些实例变量。在写程序时,如果有多个分支,往往会在某个分支中忘记了销毁,从而造成内存泄漏。另外,每次实体创建时都需要访问数据库取数据,对数据库也是一个不小的负担。我们可以思考一下,如果我们严格遵循OO,对数据库的访问都通过实体,如果实体已经创建,我们只需要到实体中取数据就可以了,根本不需要再去访问数据库。很虽然,我们需要一个缓存,我把这个任务分配给了“实体工厂”。实体就是实体工厂的产品。
下面的问题就是实体与实体工厂如何对应,在现实世界中,一个工厂会生产多个产品,所以我们的软件中也可以这样做。事实上笔者的框架的第一个版本就是这样做的,但实践后改为“一一对应”,即一个实体工厂只有一个实体产品。
至此,我们框架中的五个核心类型就全部形成了,即界面、控制器、实体工厂、实体、业务逻辑。
它们的关系如下:
图中除了这五个类型外,还有一个数据库,后面会有解释。
五个类型的职责和关系如下:
类型 |
职责 |
管理者 |
界面 |
与用户交互 从数据库中检索数据 将用户的操作恢复发送给控制器 |
用户操作 |
控制器 |
接受界面的调用,转调用后台 控制事务 控制界面的跳转 |
单例,应用程序负责销毁 |
实体工厂 |
创建实体 缓存实体 销毁实体 |
单例,应用程序负责销毁 |
实体 |
与数据库交互 |
实体工厂 |
业务 |
实现业务逻辑 |
无,局部变量,自动实例化 |
如果严格按MVC中V的定义,界面应该是没有业务逻辑的,也不应该与数据库直接交互。在我的第一个版本中,确实是这样做的。但这样做的结果是很累。在我们的数据库中,往往有很多代码与意义的对应,在表中,往往只是存储代码,在一个数据字典表中,存储代码的意义,在界面中,往往要显示意义而不是代码。而实体一般与表对应,它其中存储的只能是代码,如果界面不直接访问数据库,要想在界面中显示意义是很麻烦的。所以,现在的版本中,界面是可以直接访问数据库的,但只有读而不能写。
另外,界面中有输入检查的功能,如输入一个不存在的数据,会弹出一个查询界面列出合法的数据供用户选择。在最初的版本中,这个功能也是放到控制器来做的,同样做得很累。现在的版本中把这个功能也放到界面中去了。
一般情况下,只是把用户的界面的操作转化为对后台的调用,严格来说,控制器不应该实现业务逻辑,但在实践时,我发现有些业务逻辑也可以放到控制器中,这些业务逻辑有两个条件:
业务逻辑比较简单,如果用一个专门的业务类去处理有些“大材小用”
此业务逻辑没有重用性,只有在当前控制器服务的界面中才会调用到。
另外,控制器还有控制事务的作用。业务类和实体类是不会提交或回滚事务的,这个任务由控制器来负责。需要说明的是,如果没有界面(也就没有控制器),如在后台运行的一些服务,这时的事务就由实现此服务的业务类来控制。
实体只是负责对数据库的存取,也不会包含业务逻辑。实体只负责其自身相关的数据库表的存取。在我的最初版本中,实体是包含业务逻辑的,但在实践中渐渐放弃了这种做法。