資料庫的一種完全物件導向設計模式(包含例項) Rayphrank原創! (轉)

gugu99發表於2008-05-12
資料庫的一種完全物件導向設計模式(包含例項) Rayphrank原創! (轉)[@more@]

的一種完全面向設計 

 

1.1 完全物件導向和非完全物件導向

  物件導向(OO)方法這個名字早已深入人心,它的科學性和合理性也已毋庸置疑。人們動輒將自己開發的冠以“採用物件導向方法設計”以示其先進性就是一個極好的證明。然而,一個先進的方法學必須有相應的工具支援才能實現,它的概念和方法如不落實實現上,就不能真正掌握它的精髓而在實踐中運用。誠然,SmallTalk語言已被公認是一個面嚮物件語言,但是它對於開發者來說是多麼的陌生!C++也可以說是一個OO語言,不過從名字就可以看出他是C語言的一個變種。它實現了從過程式到物件導向程式設計的一個較好的過渡。但是許多聲稱用C++製作的軟體其實仍舊是C軟體!這是因為沒有真正掌握OO方法的緣故。


  即使開發者的開發環境,開發工具是支援OO的(如,VC),但開發者沒有以OO的觀點去觀察軟體的問題域,或者以往的過程式設計思想根深蒂固,那麼開發出的軟體仍舊是披著OO這件漂亮外衣的過程化軟體。沒有把OO作為一種確實實用的方法學。造成這種現象的原因是沒有認識到OO的精髓,尤其是在一些RAD開發工具下,更容易忽視以OO的觀點去觀察問題域。


  RAD開發工具開發的軟體常能分成兩種形式:完全物件導向設計和非完全物件導向設計。一個資料庫軟體的體系結構常能分為三層:介面層、層和資料庫層。在使用者介面層,常是一些具體與使用者互動的物件,如:按鈕、選單、和對話方塊。在資料庫層,則是從問題域中找出描述實體的表。完全物件導向和非完全物件導向的最大區別在於中介軟體層,什麼是中介軟體層,中介軟體層是問題域中具體物件,商業規則,更高層次上的問題實體。完全物件導向有中介軟體層,非完全物件導向沒有中介軟體層。RAD工具開發時常會忽視中介軟體層。


  舉個例子:一個簡單的記帳系統如果用RAD工具(如:Delphi)開發,使用者介面層是一些系統中所用的,如按鈕,選單等,這些控制元件物件由Delphi或其它開發工具中的類庫封裝;資料庫層則是在資料庫系統(如 SERVER、等)建立的表,如顧客表、產品表、訂單表等。


  完全物件導向設計中的中介軟體層為問題域的交易,商業規則等物件,以及更高層次上的問題實體,如顧客,產品,訂單。非完全物件導向則沒有中介軟體層這些物件。換句話說,非完全物件導向是基於使用者介面層直接存取資料庫層,即:基於控制元件。完全物件導向是將中介軟體層封裝資料庫層,使用者介面層使用中介軟體層,這樣將資料庫層完全透明,做到資料與介面的分離,即:物件導向,基於控制元件。


  在一些小型的系統中,非完全物件導向設計可以加快開發速度,但系統的靈活性,重用性不好,大型系統必須採用完全物件導向的開發方法,否則,由於商業規則沒有放在一起,軟體後期將不可控制,一個成功的可複用的商業軟體應該是由中介軟體層的眾多物件靈活搭配而成。


  由此,筆者提出一種資料庫完全物件導向的設計模式。

1.2資料庫的一種完全物件導向設計模式
 
  用完全物件導向的方法做一般的應用程式比資料庫程式容易一些,因為它不涉及到資料庫存取。但在做資料庫程式時應該考慮類跟跟資料庫是怎樣聯絡的。

步驟1:分析問題域,找出所有問題域中相關事物,從中抽象出物件。

步驟2:從抽象出的物件中找出所有的持久物件(Persisitant ),所謂持久物件就是由資料庫管理系統負責管理的,可以永久保留,將來可被提取的物件,將這些持久物件以“一類一表格”的原則對映到資料庫中,透過資料庫管理系統建立表格。

步驟3:定義持久物件,所有持久物件均採用“雙構造”的方法在程式中進行構造,其中,一個建構函式引數為所有初始化該持久物件的值,封裝資料模組中“存”操作,另一個建構函式的引數為唯一標識該持久物件的值,封裝資料模組中“取”操作。其它資料庫操作由持久物件相應方法封裝。

步驟4:所有與資料庫層發生互動的動作,均放在專門的資料模組中,由中介軟體層持久物件相應方法封裝,做到資料與介面的分離。


步驟5:定義其它非持久物件。具體軟體功能由相應物件協同實現。


  該設計模式的重點在於持久物件的定義,除了雙構造外,持久物件如果一次獲取資料數量>1,那麼可以定義“持久物件集”物件,“持久物件集”物件由持久物件組成,“持久物件集”物件中的物件集可由資料模組中相應的SQL語言篩選,如果資料集中資料數量非常大,那麼在資料模組中相應的SQL語言可以以固定數量進行篩選資料集,分批篩選。物件集中的相應持久物件可用連結串列的結構進行連結。
以這種方式定義的持久物件,完全封裝了資料庫存取,使用者在使用持久物件的時侯甚至感覺不到資料庫的存在,因為相應的資料庫操作已被持久物件的相應方法封裝,使用者只需要建立相應的持久物件即可進行資料庫的操作。

 

2.資料庫完全物件導向設計模式在銀行儲蓄管理系統中的實現


2.1問題域物件、持久物件與資料庫表


根據上述設計模式的思想,我們首先找出問題域的所有物件
1. 帳戶物件
2. 儲蓄帳戶物件
3. 定期儲蓄帳戶物件
4. 活期儲蓄帳戶物件
5. 銀行卡帳戶物件

  其中儲蓄帳戶物件、銀行卡帳戶物件繼承自帳戶物件,帳戶物件為一個虛基類,定期儲蓄帳戶物件、活期儲蓄帳戶物件繼承自儲蓄帳戶,儲蓄帳戶物件為一個虛基類
UML類層次圖(圖12)

圖12:帳戶物件UML類層次圖

 


6. 儲戶物件
7. 櫃員物件
8. 交易物件
9. 費用物件
10. 利率物件
11. 特殊操作物件
12. 系統資訊物件
13. 銀行功能物件
14. 銀行服務物件
15. 儲蓄服務物件
16. 銀行卡服務物件


  其中儲蓄服務物件,銀行卡服務物件繼承自銀行服務物件,銀行服務物件是一個虛基類


UML類層次圖(圖13)

圖13:服務物件類層次圖

 

  然後,我們分析問題域,將要持久在資料庫的資料物件確立為持久物件。
所確立的持久物件為:
1. 儲蓄帳戶物件
2. 定期儲蓄帳戶物件
3. 活期儲蓄帳戶物件
4. 銀行卡帳戶物件
5. 儲戶物件
6. 櫃員物件
7. 交易物件
8. 費用物件
9. 利率物件
10. 特殊操作物件
  這些持久物件將以“一類一表格”的原則對映到我們選擇的資料庫2000中,由此確立資料庫中所建立的表格與欄位為:


2.2 問題域物件的定義


(1) 雙建構函式的使用。


  限於篇幅,本文以持久物件——儲戶物件為例,說明持久物件的雙建構函式方法,其它持久物件的定義思想與之大致相同。

儲戶物件介面定義
type
  TCustomer = class(TObject)
  private
  { Private declarations }
  protected
  { Protected declarations }
  Cus_id:string;
  Cus_name:string;
  Cus_shenfenid:string;
  Cus_addr:string;
  Cus_phone:string;
  procedure New_Cus_Info(c_name,c_id,c_addr,c_phone:string);
  procedure Load_Cus_info(cusid:string);
  public
  { Public declarations }
  constructor create(c_name,c_sfid,c_addr,c_phone:string);overload;  1)
constructor Create(cusid:string);overload;  2)

  function update_cus():boolean;
  procedure Set_Cus_Id(C_Id:string);
  procedure Set_Cus_name(C_name:string);
  procedure Set_Cus_shenfenid(sfid:string);
  procedure Set_Cus_addr(addr:string);
  procedure Set_Cus_phone(phone:string);
  function Get_Cus_Id:string;
  function Get_Cus_name:string;
  function Get_Cus_shenfenid:string;
  function Get_Cus_addr:string;
  function Get_Cus_phone:string;
  published
  { Published declarations }
  end;


  根據資料庫完全物件導向設計模式,持久物件將由兩個建構函式(見(1)、(2)),其中第一個建構函式引數為所有初始化該持久物件的值,封裝資料模組中“存”操作,


constructor TCustomer.create(c_name,c_sfid,c_addr,c_phone:string);
begin
  New_Cus_Info(c_name,c_sfid,c_addr,c_phone);
end;
procedure TCustomer.New_Cus_Info(c_name,c_sfid,c_addr,c_phone:string);
begin
  Cus_name:=c_name;  1)
  Cus_shenfenid:=c_sfid;  2)
  Cus_addr:=c_addr;  3)
  Cus_phone:=c_phone;  4)
  dmodule.New_Cus_Info(self);
end;
  這裡,self指標代表持久物件自身,由於(1)—(4)已經對持久物件賦值,所以只需將self代入資料模組上對資料庫進行新增操作的方法上。

  另一個建構函式(2)的引數為唯一標識該持久物件的值,封裝資料模組中“取”操作。
constructor TCustomer.create(cusid: string);
begin
  Load_cus_info(cusid);
end;
procedure TCustomer.Load_Cus_info(cusid: string);
begin
  Cus_id:=cusid;
  dmodule.load_cus_info(self);
end;
 

  同樣self代表將取得的資料持久物件,在取得該物件所有值之前,只知道標識該物件的標識值(如儲戶物件中儲戶的編號),而資料模組上對資料庫進行取操作的函式(本例中為dmodule.load_cus_info(self);)所的功能就是對所代入的持久物件(此時只有該物件的標識值被賦值)的其他屬性值進行賦值。


procedure TDModule.Load_cus_info(var CUS: TCustomer);
begin
  with DCUSquery do
  begin
  close;
  sql.Clear;
  sql.Add(' * from 儲戶資訊表');  // (a)
  sql.add('WHERE 儲戶ID='+''''+CUS.Get_Cus_Id+'''');  // (b)
  open;
  if not fielyname('儲戶ID').isnull then
  begin
  cus.Set_Cus_name(trim(fieldbyname('儲戶姓名').asstring));  c)
  cus.Set_Cus_shenfenid(trim(fieldbyname('儲戶身份證號').asstring)); d)
  cus.Set_Cus_addr(trim(fieldbyname('儲戶地址').asstring));  e)
  cus.Set_Cus_phone(trim(fieldbyname('儲戶電話').asstring));  f)
  end;
  close;
  end;
end;

從資料庫取得資料是透過寫SQL語言 (a)、(b) 實現,將SQL返回的資料賦給持久物件的操作為(c)-(f),這樣,函式執行完畢,持久物件將擁有從資料庫查詢所取得的值。

  將對資料庫的操作封裝在資料模組中,有利於資料與介面的分離,資料模組可以作為三層資料庫中的應用服務層,使系統可以輕易轉變為三層分散式資料庫。同時,存取資料庫變得透明,使用者甚至不知道資料庫的存在。

如本例中,新存一個儲戶只需:
var  newcus:Tcustomer;
newcus:=Tcustomer.Create(‘比爾*蓋茨’,’100001’,’美國公司’,’(025)110’);

取得儲戶比爾*蓋茨(比如儲戶ID為10001)的操作為
var  newcus:Tcustomer;
newcus:=Tcustomer.Create(‘10001’);

 

  這些操作的背後已完成了對資料庫的相應操作,資料存取變得透明。而且程式的可讀性很強。
  然後這個儲戶的所有資料就可以讀取這個持久物件來實現,如我們想知道這個儲戶的地址,我們就可以這樣操作:newcus.Get_cus_addr;

因為操作物件是中介軟體層物件,將使程式可讀性非常強。

(2)“持久物件集”物件的定義
 

  在上述持久物件的定義中,由於只是取得一條記錄,所以沒有用“持久物件集”的概念,而通常應用中,經常會一次性取得後臺
資料庫的多條記錄到前臺處理,雖然也可以像上述定義持久物件那樣一條一條記錄進行存取,但這樣每新讀一條記錄都要從頭開始進行一次SQL資料庫的操作,造成資源開銷太大,降低,所以如果需要一次讀取多條記錄,就需要建立一個“持久物件集”物件。

以銀行卡交易物件為例


  一個儲戶通常會與銀行發生多次交易,當我們需要取得該儲戶銀行卡帳號的交易情況時,就需要一次性取得該帳號所發生的所有交易物件記錄,這時候取得的資料集不是單個的持久物件,而是一個持久物件的集合,這個集合裡有所有該帳號發生的交易物件。所以在這裡就需要定義“持久物件集”物件,“持久物件集”物件也是一個物件(Object),只是它的操作元素為持久物件。


在本例中,定義交易物件的持久物件集為交易集——Transacts,其宣告如下:
TTranode=^TTransactnode;
  TTransactnode=record
  TRainfo:TTransactinfo;
  Pnext:TTranode;
  end;
  TTRansacts = class(TObject)
  private
  { Private declarations }
  Pfirst:TTranode;
  Pend:TTranode;
  pos:TTranode;
  protected
  { Protected declarations }
  Transactnum:integer;
  Id_ofaccount:string;
  id_type:integer;
  public
  { Public declarations }
  function Get_TRas_acid:string;
  function Get_TRas_actype:integer;
  function Get_firsttrainfo:TTransactinfo;
  function Get_Transactnum:integer;
  function Get_lasttrainfo:TTransactinfo;
  function Get_traninfo(tra_id:string):TTRansactinfo;
  function Get_nexttraninfo():TTRansactinfo;
  procedure set_first;
  procedure TRas_add(TRa_info: TTransactinfo);
  constructor create(acid:string);
  end;
  “持久物件集”物件—交易集Transacts 採用了連結串列結構連結集合中的所有交易物件Ttransactinfo,其建構函式為連結串列初始化及集合元素的確定、新增過程。


constructor TTRansacts.create(acid:string);
begin
  TRansactnum:=0;
  Id_ofaccount:=acid;
  Pfirst:=Nil;
  pEND:=Nil;
  dmodule.load_TRAs_info(self);
end;
  資料模組中load_TRAs_info方法以交易集物件Transacts為引數,透過SQL語言篩選出所需要的交易物件,加入交易集物件的連結串列
連結串列新增的操作為:
procedure TTRansacts.TRas_add(TRa_info: TTransactinfo);
var newnode:TTranode;
begin
  new(newnode);
  newnode.TRainfo:=TRa_info;
  newnode.Pnext:=NIL;
  if Pfirst=NIL then
  begin
  pfirst:=newnode;
  pos:=pfirst;
  end
  else
  begin
  pend^.pnext:=newnode;
  end;
  pend:=newnode;
  pend^.pnext:=NIL;
  Transactnum:=Transactnum+1;
end;
  只需要宣告一個交易物件Transactinfo,就可以將該物件加入交易集物件Transacts的連結串列中。
  這裡先要介紹交易物件的定義方法,交易物件採用上述雙建構函式方法進行對資料庫單個交易記錄的封裝,
其中完成“取”操作的建構函式定義如下:
constructor TTransactinfo.Create(of_acid: string);
begin
  TRa_ACid:=of_acid;  交易物件對應的帳戶ID,交易物件的標識值
  dmodule.load_TRA_info(self)
end;
  這裡,dmodule.load_TRA_info(self),和交易集物件中dmodule.load_TRAs_info(self)是不同的,前者封裝了單個交易物件的資料庫“取”操作,後者封裝了交易集物件的資料庫“取”操作。


procedure TDModule.Load_Tra_info(var TRA:Ttransactinfo);
begin
  with Dtinfoquery do
  begin
  TRa.SET_TRa_id(fieldbyname('儲蓄帳戶交易明細ID').asstring);
  TRa.SET_TRa_ITid(trim(fieldbyname('櫃員終端編號').asstring));
  TRa.SET_TRa_Gyid(trim(fieldbyname('操作櫃員編號').asstring));
  TRa.SET_TRa_type(fieldbyname('交易型別').asinteger);
  TRa.Set_tra_money(fieldbyname('交易金額').asfloat);
  TRa.Set_tra_time(fieldbyname('交易時間').asdatetime);
  TRa.SET_tra_result(fieldbyname('交易結果').asinteger);
  TRa.SET_TRa_wdname(trim(fieldbyname('網點名稱').asstring));
  end;
end;
procedure TDModule.Load_TRas_info(var TRas: TTransacts);
begin
  with Dtinfoquery do
  begin
  close;
  sql.clear;
  sql.add('SELECT * FROM 儲蓄帳戶交易明細表');  a)
  sql.add('WHERE 儲蓄帳戶ID='+''''+TRas.Get_TRas_acid+'''');  b)
  open;
  if not fieldbyname('儲蓄帳戶ID').isnull then
  begin
  while not eof do
  begin
  TRas.TRas_add(TTransactinfo.create(TRas.Get_TRas_acid,TRas.Get_TRas_actype)); 
  c)
  next;
  end;
  end;
  close;
  end;
end;


  其中(a)、(b)為SQL語句篩選出所有指定帳號的交易記錄集,(c)是該函式的重點,先宣告一個交易物件(呼叫交易物件“取”建構函式,透過dmodule.load_TRA_info(self)完成對當前交易物件資料庫取操作),然後透過TRas.TRas_add,將取得的當前交易物件加入到交易集物件的連結串列中。


  透過持久物件集物件,可以封裝多個持久物件的資料庫存取操作,在本例中,如果要取得帳號為“1000000001”的帳戶所有交易情況,那麼只需呼叫操作
var Tras:TTransacts
Tras:=TTransacts.Create(‘1000000001’)  即可,

  所有的該帳號的交易情況已加入Tras的連結串列中,並且連結串列頭指標可透過Tras.Get_firsttrainfo得到,所有對連結串列的操作插入刪除都可以實現。如果持久物件集的集合元素數目巨大,那麼可在資料模組中SQL語言篩選的時候加上限制語句,以固定數量分批篩選。

rayphrank(to:rayphrank@sina.com">rayphrank@sina.com)版權所有,

歡迎轉載,轉載請註明作者姓名,地址!!!


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1003883/,如需轉載,請註明出處,否則將追究法律責任。

相關文章