界面代码和功能代码分离示例 ( 积分: 200 )

  • 主题发起人 主题发起人 lanyaoshi
  • 开始时间 开始时间
L

lanyaoshi

Unregistered / Unconfirmed
GUEST, unregistred user!
准备在一个项目中采用所谓的OO数据库开发方法,想实现界面代码和功能代码分离,本想找找现成的资料,但搜索了好久也没找到太多,

也看了bold、InstantObjects(没细看,感觉太复杂,可能是水平还不够看懂),最后主要参考了:Nicrosoft(奈软)的《如何将界面代码和

功能代码分离(基于Delphi/VCL)》,刘艺《面向对象编程思想》书中第8章“实现界面和业务的分离”,花了2个小时,完成了这个小示例:
1.数据操作界面:unit ufrmMain--数据的添加、修改、删除、查找,数据通过TListView显示;unit ufrmUser--数据进行添加、修改

时用来显示单个数据。
2.数据定义类:unit clsUser单元中:TUser,此类的相关属性:ID、UserName、Sex、Age对应数据库中的表中的相应字段:ID、UserName

、Sex、Age;方法ValidData用来进行数据的有效性检查。
3.数据操作类:clsUser_Operater单元中:TUser_Operater:通过ADO来操作ACCESS数据库data.mdb中的表User,生成、修改、删除数据。

代码写得比较匆忙,希望大家能指出其中的问题。我有几个不太确定的地方,希望大家一起来讨论:
1.采用动态数组来返回数据,效率如何呢?我没有进行过大数据量的测试,希望有经验的高手给个建议。
2.动态数组要释放吗?我用arrUser := nil;方式进行释放,不知是否有效。
3.何时释放动态数组比较适合:能否在生成动态数组的类的Destroy事件中释放呢?
4.最后一个问题:这样写,是否真正达到了:“界面代码和功能代码分离”这个目的?

我的联系方式:myqq8@163.com,QQ:815825

不能上传附件,只能贴代码了。我准备将示例上传到Delphi盒子上:http://www.2ccc.com/(感谢盒子多年来对我的帮助),大家可以去

那里下载。

1.数据操作界面:
--------------------------------------------------------------------------------------
unit ufrmMain;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls, Buttons, clsUser;

type
TfrmMain = class(TForm)
ListView1: TListView;
btnModi: TBitBtn;
btnAdd: TBitBtn;
btnDel: TBitBtn;
GroupBox1: TGroupBox;
Label1: TLabel;
edtKey_Name: TEdit;
btnFind: TBitBtn;
procedure FormCreate(Sender: TObject);
procedure ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
procedure btnModiClick(Sender: TObject);
procedure btnAddClick(Sender: TObject);
procedure btnDelClick(Sender: TObject);
procedure btnFindClick(Sender: TObject);
private
{ Private declarations }
FCurID: Integer;
procedure SetItemByUser(const AUser: TUser; const AListItem: TListItem);
function GetUserFromItem(const AListItem: TListItem): TUser;
public
{ Public declarations }
end;

var
frmMain: TfrmMain;

implementation
uses clsUser_Operater, ufrmUser;
{$R *.dfm}

procedure TfrmMain.SetItemByUser(const AUser: TUser; const AListItem: TListItem);
begin
if (AUser <> nil) and (AListItem <> nil) then
begin
AListItem.Caption := IntToStr(AUser.ID);
AListItem.SubItems.Clear;
AListItem.SubItems.Add(AUser.UserName);
AListItem.SubItems.Add(AUser.Sex);
AListItem.SubItems.Add(IntToStr(AUser.Age));
end;
end;

function TfrmMain.GetUserFromItem(const AListItem: TListItem): TUser;
var
AUser: TUser;
begin
Result := nil;
if AListItem <> nil then
begin
AUser := TUser.Create;
AUser.ID := FCurID;
AUser.UserName := AListItem.SubItems[0];
AUser.Sex := AListItem.SubItems[1];
AUser.Age := StrToInt(AListItem.SubItems[2]);
AUser := ufrmUser.GetUser(AUser);
Result := AUser;
end;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
var
AUser: TUser;
arrUser: TArrUser;
AUser_Operater: TUser_Operater;
AListItem: TListItem;
i, iEnd: Integer;
begin
try
AUser_Operater := TUser_Operater.Create;
arrUser := AUser_Operater.GetAllUsers;
iEnd := Length(arrUser) - 1;
for i := 0 to iEnd do
begin
AUser := arrUser;
AListItem := ListView1.Items.Add;
SetItemByUser(AUser, AListItem);
end;
finally
AUser.Free;
AUser_Operater.Free;
arrUser := nil;
end;
end;

procedure TfrmMain.ListView1SelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
begin
FCurID := StrToInt(Item.Caption);
end;

procedure TfrmMain.btnModiClick(Sender: TObject);
var
AUser: TUser;
AUser_Operater: TUser_Operater;
begin
if (ListView1.Selected <> nil) then
begin
try
AUser := GetUserFromItem(ListView1.Selected);
AUser_Operater := TUser_Operater.Create;

if AUser <> nil then
begin
AUser_Operater.SetUser(AUser);
SetItemByUser(AUser, ListView1.Selected);
ShowMessage('数据更新完毕');
end;
finally
AUser.Free;
AUser_Operater.Free;
end;
end else
ShowMessage('请选择要进行操作的数据');
end;

procedure TfrmMain.btnAddClick(Sender: TObject);
var
AUser: TUser;
AUser_Operater: TUser_Operater;
AListItem: TListItem;
begin
try
AUser_Operater := TUser_Operater.Create;

AUser := ufrmUser.GetUser(nil);
if AUser <> nil then
begin
AUser.ID := AUser_Operater.AddUser(AUser);
AListItem := ListView1.Items.Add;
SetItemByUser(AUser, AListItem);
ShowMessage('数据添加完毕');
end;
finally
AUser.Free;
AUser_Operater.Free;
end;
end;

procedure TfrmMain.btnDelClick(Sender: TObject);
var
AUser_Operater: TUser_Operater;
begin
if (ListView1.Selected <> nil) then
begin
try
AUser_Operater := TUser_Operater.Create;
AUser_Operater.DelUserByID(FCurID);
ListView1.Selected.Delete;
finally
AUser_Operater.Free;
end;
end else
ShowMessage('请选择要进行操作的数据');
end;

procedure TfrmMain.btnFindClick(Sender: TObject);
var
AUser: TUser;
arrUser: TArrUser;
AUser_Operater: TUser_Operater;
AListItem: TListItem;
i, iEnd: Integer;
begin
try
AUser_Operater := TUser_Operater.Create;
arrUser := AUser_Operater.GetUsersByUserName(edtKey_Name.Text);
iEnd := Length(arrUser) - 1;
ListView1.Items.Clear;
for i := 0 to iEnd do
begin
AUser := arrUser;
AListItem := ListView1.Items.Add;
SetItemByUser(AUser, AListItem);
end;
finally
AUser.Free;
AUser_Operater.Free;
arrUser := nil;
end;
end;

end.

--------------------------------------------------------------------------------------
unit ufrmUser;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, clsUser;

type
TfrmUser = class(TForm)
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
edtUserName: TEdit;
edtSex: TEdit;
edtAge: TEdit;
Label4: TLabel;
lblID: TLabel;
btnOK: TBitBtn;
btnCancel: TBitBtn;
procedure FormCreate(Sender: TObject);
procedure btnOKClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;


function GetUser(AUser: TUser): TUser;

implementation

{$R *.dfm}
var
PUser: TUser;

function GetUser(AUser: TUser): TUser;
var
frmUser: TfrmUser;
begin
try
Result := nil;
frmUser := TfrmUser.Create(nil);
with frmUser do
begin
if AUser <> nil then
begin
lblID.Caption := IntToStr(AUser.ID);
edtUserName.Text := AUser.UserName;
edtSex.Text := AUser.Sex;
edtAge.Text := IntToStr(AUser.Age);
end;

if ShowModal = mrOK then
begin
if AUser <> nil then
PUser.ID := AUser.ID;
Result := PUser;
end;
end;
finally
frmUser.Free;
end;
end;


procedure TfrmUser.FormCreate(Sender: TObject);
begin
PUser := TUser.Create;
end;

procedure TfrmUser.btnOKClick(Sender: TObject);
begin
PUser.UserName := edtUserName.Text;
PUser.Sex := edtSex.Text;
PUser.Age := StrToInt(edtAge.Text);
if PUser.ValidData then
Self.ModalResult := mrOK;
end;

end.
--------------------------------------------------------------------------------------
2.数据定义类:
unit clsUser;

interface
uses SysUtils,Dialogs;

type
TUser = class
private
FID: Integer;
FUserName: string;
FSex: string;
FAge: Integer;
procedure SetID(const Value: Integer);
procedure SetUserName(const Value: string);
procedure SetSex(const Value: string);
procedure SetAge(const Value: Integer);
public
property ID: Integer read FID write SetID;
property UserName: string read FUserName write SetUserName;
property Sex: string read FSex write SetSex;
property Age: Integer read FAge write SetAge;
function ValidData:Boolean;
end;


implementation

function TUser.ValidData:Boolean;
begin
Result := True;
if Length(FSex)>2 then
begin
ShowMessage('姓别的长度不能超过2个字符');
Result := False;
end;
end;

procedure TUser.SetID(const Value: Integer);
begin
FID := Value;
end;

procedure TUser.SetUserName(const Value: string);
begin
FUserName := Value;
end;

procedure TUser.SetSex(const Value: string);
begin
FSex := Value;
end;

procedure TUser.SetAge(const Value: Integer);
begin
FAge := Value;
end;


end.


--------------------------------------------------------------------------------------
3.数据操作类:
unit clsUser_Operater;

interface
uses clsUser, ADODB, SysUtils, Forms;

type
TArrUser = array of TUser;
TUser_Operater = class
private
FADOQuery: TADOQuery;
function GetMaxID:Integer;
public
constructor Create;
destructor Destroy; override;
function GetAllUsers: TArrUser;
function GetUsersByUserName(const UserName:string): TArrUser;
function GetUserByID(const ID: Integer): TUser;
procedure SetUser(const AUser: TUser);
procedure DelUserByID(const ID: Integer);
function AddUser(const AUser: TUser):Integer;
end;

implementation


constructor TUser_Operater.Create;
var
DbName: string;
begin
FADOQuery := TADOQuery.Create(nil);
DbName := ExtractFilePath(Application.ExeName) + 'Data.mdb';
FADOQuery.ConnectionString := Format('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s;Persist Security Info=False',

[DbName]);
end;

destructor TUser_Operater.Destroy;
begin
inherited;
FADOQuery.Free;
end;

function TUser_Operater.GetMaxID:Integer;
begin
Result := 0;
with FADOQuery do
begin
Close;
SQL.Text := 'SELECT MAX([ID]) FROM [User]';
Open;
if RecordCount > 0 then
Result := Fields[0].AsInteger;
end;
end;

function TUser_Operater.GetAllUsers: TArrUser;
var
arrUser: TArrUser;
AUser:TUser;
begin
Result := nil;
with FADOQuery do
begin
Close;
SQL.Text := 'SELECT [ID], [UserName], [Sex], [Age] FROM [User]';
Open;
if RecordCount > 0 then
begin
while not Eof do
begin
SetLength(arrUser, Length(arrUser) + 1);
AUser := TUser.Create;
AUser.ID := FieldByName('ID').AsInteger;
AUser.UserName := FieldByName('UserName').AsString;
AUser.Sex := FieldByName('Sex').AsString;
AUser.Age := FieldByName('Age').AsInteger;
arrUser[Length(arrUser)-1]:=AUser;
Next;
end;
Result := arrUser;
end;
end;
end;

function TUser_Operater.GetUsersByUserName(const UserName:string): TArrUser;
var
arrUser: TArrUser;
AUser:TUser;
begin
Result := nil;
with FADOQuery do
begin
Close;
SQL.Text := Format('SELECT [ID], [UserName], [Sex], [Age] FROM [User] WHERE [UserName] LIKE %s',['"%'+UserName+'%"']);
Open;
if RecordCount > 0 then
begin
while not Eof do
begin
SetLength(arrUser, Length(arrUser) + 1);
AUser := TUser.Create;
AUser.ID := FieldByName('ID').AsInteger;
AUser.UserName := FieldByName('UserName').AsString;
AUser.Sex := FieldByName('Sex').AsString;
AUser.Age := FieldByName('Age').AsInteger;
arrUser[Length(arrUser)-1]:=AUser;
Next;
end;
Result := arrUser;
end;
end;
end;

function TUser_Operater.GetUserByID(const ID: Integer): TUser;
var
AUser: TUser;
begin
Result := nil;
with FADOQuery do
begin
Close;
SQL.Text := 'SELECT [ID], [UserName], [Sex], [Age] FROM [User] WHERE [ID]=' + IntToStr(ID);
Open;
if RecordCount > 0 then
begin
AUser := TUser.Create;
AUser.ID := FieldByName('ID').AsInteger;
AUser.UserName := FieldByName('UserName').AsString;
AUser.Sex := FieldByName('Sex').AsString;
AUser.Age := FieldByName('Age').AsInteger;
Result := AUser;
end;
end;
end;

procedure TUser_Operater.SetUser(const AUser: TUser);
begin
with FADOQuery do
begin
Close;
SQL.Text := 'UPDATE [User] SET [UserName]=:UserName, [Sex]=:Sex, [Age]=:Age WHERE [ID]=:ID';
Parameters.ParamByName('UserName').Value := AUser.UserName;
Parameters.ParamByName('Sex').Value := AUser.Sex;
Parameters.ParamByName('Age').Value := AUser.Age;
Parameters.ParamByName('ID').Value := AUser.ID;
ExecSQL;
end;
end;

procedure TUser_Operater.DelUserByID(const ID: Integer);
begin
with FADOQuery do
begin
Close;
SQL.Text := 'DELETE FROM [User] WHERE [ID]=:ID';
Parameters.ParamByName('ID').Value := ID;
ExecSQL;
end;
end;

function TUser_Operater.AddUser(const AUser: TUser):Integer;
var
ID:Integer;
begin
ID:=GetMaxID+1;
with FADOQuery do
begin
Close;
SQL.Text := 'INSERT INTO [User] ([ID],[UserName],[Sex],[Age]) VALUES (:ID, :UserName, :Sex, :Age)';
Parameters.ParamByName('ID').Value := ID;
Parameters.ParamByName('UserName').Value := AUser.UserName;
Parameters.ParamByName('Sex').Value := AUser.Sex;
Parameters.ParamByName('Age').Value := AUser.Age;
ExecSQL;
end;
Result := ID;
end;

end.


--------------------------------------------------------------------------------------
 
我认为真正要做到分离就不仅仅是逻辑上的分离,而是要物理上的分离
比如你的代码,虽然都表面上表示层和业务层以及数据库层是分离的,可是如果要更改其中一个层,还是要重新编译整个程序,这和以前比有什么好处吗?好处可能是各个层次更独立一点。
要做到物理上的分层可以借助动态链接库,将业务层和数据库层分别放入一个动态链接库中,这样修改一个层次的内容就不影响其他,修改后替换一下就可以了,真正做到复用。
要从动态链接库中导出对象有两种方法,
一是使用抽象类
二是使用接口,个人认为接口将是一个趋势。

定义一个接口,比如你的TUser类里面的方法定义在接口中
然后用TUser类实现这个接口
整个代码封装在DLL中
在DLL中提供一个输出函数输出这个接口

界面层是一个EXE,里面引用接口,然后使用TUser的对象时候就是声明一个接口,调用DLL中的输出函数并赋值给这个接口,之后使用这个接口的方法就可以了
非常简单
 
感谢muhx参与,给我比较大的提示。但这样写,会不会产生很多DLL呢?比如数据库中有10个表,如USER1,USER2,USER3...USER10,是不是要分别写上10个DLL进行封装呢?
 
可能会有很多接口,将涉及数据库的部分放在一个DLL不就行了
看你自己怎么组合了
 
明白了,muhx,能否留个联系方式?有时间多多交流,可发到我的邮箱:myqq8@163.com
 
muhx1981@126.com
QQ 38778842
 
你们都是专业开发的吗?
都是高手,俺看不懂。。哈哈
不知能否认识哈两位呢,向你们学习!!!
QQ:249892859
Email:ws_tan1985@163.com
 
看不懂只是说明你现在暂时可能不用这样的模式来写代码。很高兴能交个朋友。
 
我也喜欢研究OOP,可是技艺不精,在以前的公司看到过一个高手写的,源代码没有了,凭记忆自己了一个Demo,拿出来大家探讨下。
 
查询主类
unit U_Sql;

interface

uses
SysUtils, Classes, DB, ADODB;

type
TSql = class(TObject)
private

protected
FAdoquery:TAdoquery;
public
constructor Create;
destructor Destroy; override;
function ExecSql(Sqls:String; isUpdate:Boolean):Boolean; virtual;
function GetSqls(Index:Integer):String; virtual;
published

end;

implementation

{ TSql }

constructor TSql.Create;
begin
FAdoquery := TAdoquery.Create(nil);
end;

destructor TSql.Destroy;
begin
FreeAndNil(FAdoquery);
inherited;
end;

function TSql.ExecSql(Sqls: String; isUpdate: Boolean): Boolean;
begin
result := True;
try
with FAdoquery do
begin
Close;
Sql.Clear;
Sql.Add(Sqls);
if isUpdate then ExecSql
else Open;
end;
except
result:=False;
end;
end;

function TSql.GetSqls(Index: Integer): String;
begin
result:='';
end;

end.
派生类TLogin DataModule
unit U_Login;

interface

uses
SysUtils, Classes, DB, ADODB, U_Sql;

type
TDataModule2 = class(TDataModule)
ADOQuery1: TADOQuery;
private
{ Private declarations }
public
{ Public declarations }
end;

TLogin = class(TSql)
private

protected

public
constructor Create(ADOQuery:TADOQuery1);
destructor Destroy; override;
function GetSqls(Index:Integer):String; override;
published

end;


var
DataModule2: TDataModule2;

implementation

{$R *.dfm}

{ TLogin }

constructor TLogin.Create(ADOQuery:TADOQuery1);
begin
FAdoquery := ADOQuery;
end;

destructor TLogin.Destroy;
begin

inherited;
end;

function TLogin.GetSqls(Index: Integer): String;
begin
case Index of
0: Result := 'Select username,password from UserInfo where 1<>2' ;
1: Result := '';
else Result := '';
end;
end;

end.
 
GetSqls用来获取SQL语句,这样写有什么好处呢,请指教。
 
物理分离当然是最好了。
我觉得,实现“界面和业务”的分离,首先是使用封装来进行各个层面上的类的封装,而封装是利用接口来实现层面上的通信的,要做到最小化也就是避免修改一处,而动其他的地方的话,那就是把接口尽量做得最小,也就是暴露最小化,封装最大化。就是降低各个层面上的耦合度。这样才能使你的封装的类强壮和有持久性。
 
都是高手啊,正想学习
 
通用性的作用,将用到的SQL语句都封装在方法里,用的时候通过索引值来返回
 
呵呵,laobeli是把SQL封装在方法了啊,放在数据库中好点吧,假如是C/S模式的话,大家对界面分离有什么见解?
 
楼主好像把数据库操作和业务放在一起了?
为什么不把数据库操作和业务分开?
 
sislcb说得没错,我把数据库操作和业务放在一起了,因为第一次写这样的程序。不知如何分开数据库操作和业务?能否举实例说明一下?
 
我还有一个疑问请大家帮忙看看,数据定义类TUser的属性ID,UserName,Sex,Age与数据库中表User的字段是一一对应的,字段少时也许好办,如字段有很多,如50多个,是否每一个字段都要封装为类的属性呢?如果全部封装,有些时候我们只想获取其中某些字段的值,如只想获取UserName,但类TUser返回了所有的字段,当数据量大时效率应该会比较低了,应该如何解决一个难题呢?是否又要定义另一个类TUser2,它的属性只包括UserName,但这样一样,与表User相关的会不会要定义很多类呢。
 
面向对象的本质是为了更符合人的思维习惯 .. 而程序的正确性与执行效率总是相对的 .. OO的设计必然会带来效率上的开支,而最终要看的是程序的效率和延展性哪个才是你的追求?松耦合的OO程序不能只看其实现的功能 ..
 
后退
顶部