DA06 – 强类型DataTable和 Business Helper 类
来源:互联网 发布:ar扫描软件 编辑:程序博客网 时间:2024/05/17 21:46
DA06 – 强类型DataTable和 Business Helper 类
本文说明了如何将业务规则封装为可重用动态的类,如何在无损DataTable灵活性的前提下使用强类型字段.也展示了用于服务端开发和业务处理器相类似的概念.
注意: 本文引用的范例可以在文档底部的连接中下载.
DataTable, RAD 和用于事件处理的设计问题
DataTable在概念上非常类似于DataSet,并扩展了动态方法绑定功能,规则的表达式支持,面向对象的Delta和其他特性.通常在客户端使用DataTables,同使用Borland的TClientDataset和Developer Express的TdxMemData内存数据集一样通过数据敏感控件显示和编辑数据.
尽管很相似,DataTable不支持定义固定的字段.因为这样的话需要大量使用RAD(事件驱动)性质的应用程序代码,将很难使业务对象隔离为可重用单元.
当你增加一个如BeforePost或AfterInsert事件处理后,Delphi自动在含Dataset的单元(可能是窗体或Data Module)中添加一个函数.现在假设有10个或更多的TDataset组件需要使用BeforePost和AfterInsert事件处理.你的Data Module将很快变成了杂乱的函数集合,将无法重用也不支持优美的封装.
使用这种技术创建的系统在开发几个月后将成为维护的噩梦,RAD很快变为Rapid Application Disaster(快速应用灾难)的缩写.
Data Abstract对这种问题提供了优美的解决方案,允许你将DataTable作为创建非数据感知应用时的纯粹对象.
Data Abstract 解决方案
Data Abstract解决上面提到的问题(和其他问题)使用了强类型数据集, 强类型的业务处理器和业务帮助(business helper)类. 某种程度上允许你像ADO.NET的强类型数据集一样使用,但是在下面你还将看到一些扩展概念.
提示:解决方案结合了制造商模式(Builder pattern)和策略设计模式(Strategy pattern)的变异.
制造商模式(Builder pattern)允许客户端对象通过特定类型和参数去创建其它对象.将客户端与对象构造细节隔离开.
策略设计模式(Strategy pattern )组合主机的底耦合算法并封装在单独的类中.允许你在任何时间选择使用这些算法.
关于这两个模式及我们产品中实现的其它模式,你可以查看《设计模式》(Design Patterns) (作者Addison-Wesley,Gamma, Helm, Johnson, Vlissides) 和《企业应用架构模式》(Patterns of Enterprise Application Architecture)作者(Addison-Wesley,Fowler).
制造商模式(Builder pattern)允许客户端对象通过特定类型和参数去创建其它对象.将客户端与对象构造细节隔离开.
策略设计模式(Strategy pattern )组合主机的底耦合算法并封装在单独的类中.允许你在任何时间选择使用这些算法.
关于这两个模式及我们产品中实现的其它模式,你可以查看《设计模式》(Design Patterns) (作者Addison-Wesley,Gamma, Helm, Johnson, Vlissides) 和《企业应用架构模式》(Patterns of Enterprise Application Architecture)作者(Addison-Wesley,Fowler).
强类型数据表是由正常的DataTable转化为类型化的特殊业务接口,以便于可以通过属性去存取字段,或调用你可能加入的附加方法. 接下来我们将看看如何生成这个接口.
现在我们看看下图对结果产生一个基本的认识 (注意代码提示列表).你需要理解ICustomers定义和为你生成的代码:
虽然同以前使用FieldByName或Fields集合相比有很多进步,你可能认为使用固定的字段也很容易. 也不全对,你可以检查TStronglyTypedClientMainForm 类的定义:
由于Data Abstract的强类型接口,你的Data Module和窗体不再需要声明很多TField组件了. 表结构的定义与其拥有者模块完全隔离,并包含在一个特定的业务单元中.
这对于维护和系统设计来说是一大完全的进步.
当你你能向你的类增加特定的行为(如TCustomer.CheckBalance方法)又能保证与其拥有者保持隔离吗?当然!看如下截图:
注意DataTable已经类型转换为IAdvancedCustomer 而不是 ICustomers.如你希望的这个方法与StronglyTypedClientMainForm是隔离的.
这些都是通过使用业务帮助类(business helper classes)实行的.我们将在本文说明它们是什么如何巧妙的运用.在说明这些之前,我们需要看一下如何使用IDE向导生成强类型类和接口(如 ICustomers).
强类型单元的IDE向导
打开强类型范例的项目组,选择服务端并打开StronglyTyped Service服务.
此模块的Data Abstract schema只包含一个叫做Customers的DataTable.如下图:
使用Data Abstract schemas使生成强类型单元的过程简单自动.右击主窗体的Schema控件点击如下项:
向导提示你输入两个单元的名字:
- 客户端业务单元:这个单元中包含一些接口如ICustomers和IOrders以及实现它们的基础类(见下面的TCustomersDataTableRules).这些类包含了DataTable字段向属性影射的代码. 你可以继承它们执行你自己的特殊业务规则和方法.这个范例可在uBizCustomersClient.pas单元中发现.
- 服务端业务单元: 这个单元引用上一个并扩展其类型支持服务端Delta. 你将发现一些接口的定义(如ICustomersDelta)和相关的实现类 (见下面的TBizCustomerServerRules). 你可以像在客户端一样继承这些类并实现你的业务逻辑规则.这个例子包含在 uBizCustomersServer.pas 单元.
unit SampleSchemaClient_Intf;
interface
[..]
const
{ Data table rules ids
Feel free to change them to something more human readable
but make sure they are unique in the context of your application }
RID_Customers = '{A97B58B8-3C56-413D-BA55-360BCD6ACBEA}';
RID_Orders = '{63B9B897-D9BC-430C-9D81-C0466A5CD6AD}';
{ Data table names }
nme_Customers = 'Customers';
nme_Orders = 'Orders';
{ Customers fields }
fld_CustomersCustomerID = 'CustomerID';
fld_CustomersCompanyName = 'CompanyName';
[..]
{ Customers field indexes }
idx_CustomersCustomerID = 0;
idx_CustomersCompanyName = 1;
[..]
type
{ ICustomers }
ICustomers = interface
['{555F0253-7185-47B4-86D9-0CCCF239EBBE}']
// Property getters and setters
function GetCustomerIDValue: String;
procedure SetCustomerIDValue(const aValue: String);
function GetCustomerIDIsNull: Boolean;
procedure SetCustomerIDIsNull(const aValue: Boolean);
function GetCompanyNameValue: String;
procedure SetCompanyNameValue(const aValue: String);
function GetCompanyNameIsNull: Boolean;
procedure SetCompanyNameIsNull(const aValue: Boolean);
[..]
// Properties
property CustomerID: String read GetCustomerIDValue
write SetCustomerIDValue;
property CustomerIDIsNull: Boolean read GetCustomerIDIsNull
write SetCustomerIDIsNull;
property CompanyName: String read GetCompanyNameValue
write SetCompanyNameValue;
property CompanyNameIsNull: Boolean read GetCompanyNameIsNull
write SetCompanyNameIsNull;
[..]
end;
{ TCustomersDataTableRules }
TCustomersDataTableRules = class(TDADataTableRules, ICustomers)
private
protected
// Property getters and setters
function GetCustomerIDValue: String; virtual;
procedure SetCustomerIDValue(const aValue: String); virtual;
function GetCustomerIDIsNull: Boolean; virtual;
procedure SetCustomerIDIsNull(const aValue: Boolean); virtual;
function GetCompanyNameValue: String; virtual;
procedure SetCompanyNameValue(const aValue: String); virtual;
function GetCompanyNameIsNull: Boolean; virtual;
procedure SetCompanyNameIsNull(const aValue: Boolean); virtual;
[..]
// Properties
property CustomerID: String read GetCustomerIDValue
write SetCustomerIDValue;
property CustomerIDIsNull: Boolean read GetCustomerIDIsNull
write SetCustomerIDIsNull;
property CompanyName: String read GetCompanyNameValue
write SetCompanyNameValue;
property CompanyNameIsNull: Boolean read GetCompanyNameIsNull
write SetCompanyNameIsNull;
[..]
public
constructor Create(aDataTable : TDADataTable); override;
destructor Destroy; override;
end;
{ IOrders }
IOrders = interface(IDAStronglyTypedDataTable)
['{50A479DE-60BA-4066-AAEB-B840FC045BBB}']
[..]
implementation
uses Variants;
{ TCustomersDataTableRules }
constructor TCustomersDataTableRules.Create(aDataTable : TDADataTable);
begin
inherited;
end;
destructor TCustomersDataTableRules.Destroy;
begin
inherited;
end;
function TCustomersDataTableRules.GetCustomerIDValue : string;
begin
result := DataTable.Fields[idx_CustomersCustomerID].Asstring;
end;
procedure TCustomersDataTableRules.SetCustomerIDValue(const Value : string);
begin
DataTable.Fields[idx_CustomersCustomerID].Asstring := Value;
end;
[..]
initialization
RegisterDataTableRules(RID_Customers, TCustomersDataTableRules);
RegisterDataTableRules(RID_Orders, TOrdersDataTableRules);
end.
可见有一个接口 (ICustomers)就有一个实现它的类 (TCustomersDataTableRules) 可一个叫做RegisterDataTableRules的方法的调用.我们将说明类和方法调用的用意.
这里是服务端业务单元部分代码:
unit SampleSchemaServer_Intf;
interface
uses
Classes, DB, SysUtils, uROClasses, uDADataTable,
uDABusinessProcessor, SampleSchemaClient_Intf;
const
{ Delta rules ids
Feel free to change them to something more human readable
but make sure they are unique in the context of your application }
RID_CustomersDelta = '{A21D52B7-F0E3-4815-B0D3-FE378A08A79A}';
RID_OrdersDelta = '{AD299B05-275D-495E-8036-3B383CDA5248}';
type
{ ICustomersDelta }
ICustomersDelta = interface(ICustomers)
['{79206377-2D23-4B53-84A3-AA445FF02FA8}']
// Property getters and setters
function GetOldCustomerIDValue : string;
function GetOldCompanyNameValue : string;
[..]
// Properties
property OldCustomerID : string read GetOldCustomerIDValue;
property OldCompanyName : string read GetOldCompanyNameValue;
[..]
end;
{ TCustomersBusinessProcessorRules }
TCustomersBusinessProcessorRules = class(TDABusinessProcessorRules,
ICustomers, ICustomersDelta)
private
protected
// Property getters and setters
function GetCustomerIDValue : string; virtual;
function GetCustomerIDIsNull: Boolean; virtual;
function GetOldCustomerIDValue : string; virtual;
function GetOldCustomerIDIsNull: Boolean; virtual;
procedure SetCustomerIDValue(const Value : string); virtual;
procedure SetCustomerIDIsNull(const aValue: Boolean); virtual;
function GetCompanyNameValue : string; virtual;
function GetCompanyNameIsNull: Boolean; virtual;
[..]
// Properties
property CustomerID : String read GetCustomerIDValue
write SetCustomerIDValue;
property CustomerIDIsNull : Boolean read GetCustomerIDIsNull
write SetCustomerIDIsNull;
property OldCustomerID : String read GetOldCustomerIDValue;
property OldCustomerIDIsNull : Boolean read GetOldCustomerIDIsNull;
property CompanyName : String read GetCompanyNameValue
write SetCompanyNameValue;
property CompanyNameIsNull : Boolean read GetCompanyNameIsNull
write SetCompanyNameIsNull;
property OldCompanyName : String read GetOldCompanyNameValue;
property OldCompanyNameIsNull : Boolean read GetOldCompanyNameIsNull;
[..]
public
constructor Create(aBusinessProcessor : TDABusinessProcessor);
destructor Destroy; override;
end;
[..]
implementation
uses Variants;
{ TCustomersBusinessProcessorRules }
constructor TCustomersBusinessProcessorRules.Create(
aBusinessProcessor : TDABusinessProcessor);
begin
inherited;
end;
destructor TCustomersBusinessProcessorRules.Destroy;
begin
inherited;
end;
function TCustomersBusinessProcessorRules.GetCustomerIDValue: String;
begin
result := BusinessProcessor.CurrentChange.NewValueByName[ fld_CustomersCustomerID];
end;
function TCustomersBusinessProcessorRules.GetCustomerIDIsNull: Boolean;
begin
result := VarIsNull(BusinessProcessor.CurrentChange.NewValueByName[ fld_CustomersCustomerID]);
end;
function TCustomersBusinessProcessorRules.GetOldCustomerIDValue: String;
begin
result := BusinessProcessor.CurrentChange.OldValueByName[ fld_CustomersCustomerID];
end;
function TCustomersBusinessProcessorRules.GetOldCustomerIDIsNull: Boolean;
begin
result := VarIsNull(BusinessProcessor.CurrentChange.OldValueByName[ fld_CustomersCustomerID]);
end;
[...]
initialization
RegisterBusinessProcessorRules(RID_CustomersDelta,
TCustomersBusinessProcessorRules);
RegisterBusinessProcessorRules(RID_OrdersDelta,
TOrdersBusinessProcessorRules);
end.
可见,这个单元扩展了客户端类型并提供了一个增强的类型查看客户端生成的Delta.你可能希望在上例的服务端利用代码策略处理Delta.
Data Abstract的业务帮助类( Business Helper classes)
TCustomersDataTableRules 和TCustomersBusinessProcessorRules是业务帮助类. 它们的目的是实现属性的Getter和Setter并提供一个继承基础类.你可以在单独的单元重创建多个子类实现你的业务代码.
我们正是在uBizCustomersClient.pas和uBizCustomerServer.pas单元中通过创建两个子类实现了真正的业务逻辑.这是uBizCustomersClient.pas中的代码:
unit uBizCustomersClient;
interface
uses
Classes, SysUtils,
uDADataTable, SampleSchemaClient_Intf,
uDABusinessProcessor, SampleSchemaServer_Intf,
StronglyTypedLibrary_Intf;
{ IAdvancedCustomer }
IAdvancedCustomer = interface(ICustomers)
['{BDB203DC-954B-4D78-A446-B1E2232BEF71}']
function GetOrders : IOrders;
function CheckBalance : currency;
procedure DisableAccount;
property Orders : IOrders read GetOrders;
end;
{ TBizCustomersClientRules }
TBizCustomersClientRules = class(TCustomersDataTableRules, IAdvancedCustomer)
private
protected
// Business events
procedure AfterInsert(Sender : TDADataTable); override;
procedure BeforeDelete(Sender : TDADataTable); override;
procedure BeforePost(Sender : TDADataTable); override;
// IAdvancedCustomer
function CheckBalance : currency;
procedure DisableAccount;
function GetOrders : IOrders;
end;
{ TBizOrdersClientRules }
TBizOrdersClientRules = class(TOrdersDataTableRules)
protected
procedure OnNewRecord(Sender: TDADataTable); override;
procedure BeforePost(Sender : TDADataTable); override;
end;
{ TBizCustomerIDRules }
TBizCustomerIDRules = class(TDAFieldRules)
private
protected
procedure OnValidate(Sender: TDACustomField); override;
procedure OnChange(Sender: TDACustomField); override;
end;
{ General validation routine shared by client and server }
procedure ValidateCustomer(const aCustomers : ICustomers);
implementation
uses uDARemoteDataAdapter;
const
def_CompanyName = 'New Company';
def_ContactName = '<Unknown>';
{ General validation routine shared by client and server }
procedure ValidateCustomer(const aCustomers : ICustomers);
var errors : string;
begin
errors := '';
with aCustomers do begin
if (Trim(CustomerID)='') then
errors := errors+'CustomerID cannot be empty'+#13;
if (Trim(CompanyName)='') then
errors := errors+'CompanyName is required'+#13;
if (errors<>'') then
raise EDABizValidationException.Create(errors);
end;
end;
procedure ValidateOrder(const aOrder : IOrders);
var errors : string;
begin
errors := '';
with aOrder do begin
if (Trim(CustomerID)='') then
errors := errors+'An order must have a CustomerID'+#13;
if (EmployeeID<=0) then // 0 also covers NULL in the conversion of AsInteger
errors := errors+'Invalid or unspecified EmployeeID'+#13;
if (errors<>'') then
raise EDABizValidationException.Create(errors);
end;
end;
[..]
initialization
RegisterDataTableRules('ClientRules.Customers', TBizCustomersClientRules);
RegisterDataTableRules('ClientRules.Orders', TBizOrdersClientRules);
RegisterFieldRules('CustomerID', TBizCustomerIDRules);
end.
你可能猜到, TDADataTableRules 类(最终继承TBizCustomersClientRules)负责实现DataTable的时间处理(如 BeforePost, AfterInsert等):
{ TDADataTableRules }
TDADataTableRules = class(TDABusinessRules, IDAStronglyTypedDataTable, IDARangeController)
private
fDataTable: TDADataTable;
fDetails : TStringList;
function GetDetails(Index: integer): TDADataTable;
function GetDetailsCount: integer;
protected
// Misc
function GetDataTable: TDADataTable;
procedure Attach(aDataTable : TDADataTable); virtual;
procedure Detach(aDataTable : TDADataTable); virtual;
[..]
// Business events
procedure BeforeOpen(Sender: TDADataTable); virtual;
procedure AfterOpen(Sender: TDADataTable); virtual;
procedure BeforeClose(Sender: TDADataTable); virtual;
procedure AfterClose(Sender: TDADataTable); virtual;
procedure BeforeInsert(Sender: TDADataTable); virtual;
[..]
public
constructor Create(aDataTable : TDADataTable); virtual;
destructor Destroy; override;
end;
TCustomersBusinessProcessorRules继承自TDABusinessProcessorRules并提供TDABusinessProcessor的事件处理.
这是uBizCustomersServer.pas单元的部分代码:
unit uBizCustomersServer;
{
This unit contains the business rules handlers for the server application.
It enforces additional rules that might change over time. This is a good
example to show the advantages of a multi-tier architecture: systems can be
updated via a server re-deploy without the need to update any client.
It's important to notice how some business rules are shared among clients and
servers. In particular, Customer validation is done by calling the
ValidateCustomers function (from uBizCustomersClient.pas).
This is not a requirement but a highly desirable practice, expecially when
your system is accessed by clients that were not developed by you (e.g. Java
clients accessing your server through SOAP).
For additional notes, such as how to extend the business functionality by
adding custom interfaces, refer to the comments in the
uBizCustomersClient.pas unit
}
interface
uses
Classes, SysUtils,
uDADataTable,uDADelta,
uBizCustomersClient, uDAInterfaces,
uDABusinessProcessor, SampleSchemaServer_Intf;
type
{ TBizCustomerServerRules }
TBizCustomerServerRules = class(TCustomersBusinessProcessorRules)
protected
// Business events
procedure BeforeProcessChange(Sender: TDABusinessProcessor;
aChangeType: TDAChangeType;
aChange: TDADeltaChange;
var ProcessChange: boolean); override;
end;
implementation
{ TBizCustomerServerRules }
procedure TBizCustomerServerRules.BeforeProcessChange(
Sender: TDABusinessProcessor; aChangeType: TDAChangeType;
aChange: TDADeltaChange; var ProcessChange: boolean);
begin
inherited;
if (aChangeType<>ctDelete) then ValidateCustomer(Self);
// It's a sort of a strong rule but it's just to make a point that server side
// business rules might enforce stronger rules than clients.
if (aChangeType=ctInsert) and not SameText(ContactName, 'Alex') then
raise Exception.Create('Cannot process an update without Alex as ContactName');
ContactTitle := TimeToStr(Now);
end;
initialization
RegisterBusinessProcessorRules('ServerRules.Customers',TBizCustomerServerRules);
end.
只有一个要素我们还没有讨论:如何向TDADataTable 和TDABusinessProcessor管理业务逻辑帮助类?这是在上面单元的Initialization小节中调用RegisterXXXRules设置匹配的 BusinessRulesID 属性实现的.
这样设置以后,DataTable和业务处理器将创建与这个ID(RegisterDataTableRules 和RegisterBusinessProcessorRules的第二个参数)匹配的类的实例,使之操作你要在代码中使用的业务接口支持.
提示:你可能主要到, TBizCustomersClientRules实现了IAdvancedCustomer接口.同样你也可以增加其它接口并可以使用支持VCL功能或QueryInterface将DataTable类型转换为这些接口.这允许你检测DataTable及其帮助类支持哪个接口.
结论
Data Abstract强类型数据表和业务处理器通过增加类型安全的DataTable大大的改进了代码的可读性和完整性. 通过使用业务帮助类和接口类型转换,你可以与凌乱的Data Module说Good-bye了,并以RAD的方式使用纯粹清晰的面向对象技术.
Data Abstract架构允许你清晰的将客户端和服务端业务规则分离,并在需要的时候再利用共享块(如 ValidateCustomers 函数).为多层数据接口以RAD的方式提供面向对象支持!
- DA06 – 强类型DataTable和 Business Helper 类
- 强类型和弱类型
- 类型强转和地址强转
- 非强类型dataset 和 强类型dataset 比较
- 弱类型集合和强类型集合
- 强类型和弱类型语言
- 语言的强类型和弱类型
- 弱类型语言和强类型语言
- 强类型语言和弱类型语言
- 强类型语言和弱类型语言
- 强类型语言和弱类型语言
- 弱类型语言和强类型语言
- 强类型语言和弱类型语言
- 【Helper】泛型类和集合类的方法
- helper类
- 一个强类型ArrayList类。
- 强/软/弱/虚引用和强/弱类型
- Newtonsoft.Json转换强类型DataTable错误:Self referencing loop detected with type ......
- Serializable兼容性问题及serialVersionUID的使用
- Java编程那些事儿14——Eclipse基础使用进阶
- java多态编程实例应用
- Windows系统下文件不能共享的解决方法
- 常用语音编码的WAVE文件头格式剖析
- DA06 – 强类型DataTable和 Business Helper 类
- AD7658应用心得
- 几何学悖论
- J2SE中的序列化之接受默认序列化
- 对TCP和UDP的理解
- Hello World by Microsoft Speech SDK 5.1
- Delphi控件的通用删除方法
- Dillo 0.86文档翻译 --- DwStyle.txt (转载)
- linux 成功安装 vsftpd 手记