在Delphi里有个特殊的模块DataModule,这是一个运行时不可见的模块,主要用来放与数据库交互的控件,例如数据库连接控件TConnection。其他模块需要与数据库交互的话,就引入这个单元。例如吧界面上的Query的Connection指定为DataModule里的Connection控件。
Delphi早期版本本身带了BDE版本的数据库操作控件集,然后又提供了封装了微软的ADO的控件集(以前就叫ADO,XE之后改名为dbGo),后来又提供了dbExpress控件集,以及FDAC控件集。还有众多的第三方数据库控件集,如uniDAC等。这些控件集提供了自己特别的一组控件,连接数据库的Connection,操作数据表的Table或Query等;这些控件都只能跟自己家控件集里的其他控件交互,ADO的Query无法使用BDE或者FDAC的Connection。
竟然存在这么多的数据库控件集!!那么问题来了,如果我们平时采用拖拽控件的方式进行设计,某天基于某个特别的理由,我们想要更换一个数据库控件集,那该怎么办?这里可能就要调整下原来的设计方式了。我们可能无法再使用面向数据集编程的快速开发方式了;这也将失去Delphi强大的快速开发,快速搭建一个可以操作的用户UI的能力。
不过,再仔细分析下,其实我们也只是失去了数据集控件的直接Post提交数据修改的能力。界面上我们可以通过内存表(早期版本可以用TClientDataSet,有FDAC的版本可以使用TFDMemTable)来提供映射数据表的能力;然后对提交数据修改结果部分稍作修改,写成执行SQL语句(Insert、Update,Delete)的方式,也并没有失去太多的Delphi的快速开发能力。如果把执行SQL语句的功能封装在一个基类或者辅助类里,其实多花费的时间也就是设计一个SQL辅助类;这个辅助类很容易就可以实现,估计花个半天时间就可以编写及测试完毕。
好了,有了设计思路,DataModule提供数据库交互能力(与具体的数据库控件集绑定),UI上放内存表控件(TClientDataSet或TFDMemTable,Delphi后期版本建议使用TFDMemTable,数据操作更方便简单)。
新的问题来了,我们仍然需要在UI单元里引用DataModule单元,否则无法与数据库实现交互;但是如果我们引用了DataModule单元,也意味着我们与具体的数据库控件集也绑定了,后期想切换数据库控件集,也需要大量修改UI的DataModule引用。为了避免这种问题发生,我们可以采用接口的设计方式,设计一个IDatabaseIntf,声明select,execute, transaction manage等数据库交互功能,然后在DataModule里加入IDatabaseIntf的实现。同时在接口单元里提供一个Database工厂类DatabaseFactory,实现一个getDatabase方法,返回IDatabaseIntf。用户UI单元里引用这个IDatabaseIntf单元就可以了。
unit common.IDatabaseIntf;
uses
Data.DB;
type
IDatabaseIntf = interface
function select(sql: string): TDataSet;
procedure execute(sql: string);
procedure beginTrans;
procedure commitTrans;
procedure rollbackTrans;
end;
TDatabaseFactory = class
private
fisDefault: booelan;
fDatabase: TDatabaseIntf;
function getDefaultDatabase: IDatabaseIntf;
public
destructor Destroy; override;
//获取数据库操作接口(前端调用)
function getDatabase: TDatabaseIntf;
//注册数据库接口
procedure registerDatabase(database: IDatabaseIntf);
end;
var
DatabaseFactory: TDatabaseFactory; //数据库接口工厂
implemention
uses
common.database.ado;
{ TDatabaseFactory }
destructor TDatabaseFactory.Destroy;
begin
if fisDefault then
fDatabase := nil;
inherited;
end;
functon TDatabaseFactory.getDatabase: TDatabaseIntf;
begin
if not assigned(fDatabase) then
fDatabase := getDefaultDatabase;
result := fDatabase;
end;
procedure TDatabaseFactory.registerDatabase(database: IDatabaseIntf);
begin
fDatabase := database;
end;
function TDatabaseFactory.getDefaultDatabase: IDatabaseIntf;
begin
result := TdatabaseADO.Create(nil); //databaseADO是全局变量,在ADO数据库交互单元里声明
fisDefault := true;
end;
initialization
DatabaseFactory := TDatabaseFactory.Create;
finalization
DatabaseFactory.Free;
end.
工厂类里提供一个registerDatabase方法,这样就不需要在接口单元引入具体的数据库实现单元了(默认实现单元必须要引入),也就达到了UI单元与具体的数据库操作单元解耦的目的。