使用Delphi 發展商業物件
作者: 陳國生
(一) 前言
Delphi RAD的設計一直存在兩種正負面的評價,原因是在要兼顧視覺化設計的優點,又要符合物件導向設計的方法,往往有所衝突。本文主要探討Delphi 在現有框架上如何實現商業物件的設計,及應該注意一些的設計要領。
商業物件的目的就希望將系統元件化,並達到重複使用的目的,以降低軟體開發成本,在使用上我們又希望維持Delphi 這種視覺化的設計。由於受限於Delphi TDataSet 的資料模型,許多協力廠商,在資料的持續層上開發R-O maping 以便讓商業物件的開發更加容易,以下列出一些商業元件廠商所開發的相關產品﹕
BoldSoft co.InstantObjects™tiOPF
本文則是探討在現有架構下,如何達成商業物件的設計。
(二) DataModule 的設計
以物件的裝封,繼承,多型的特性而言,無疑的TDataModule 都符合這個要件,同時TDataModule本身可以結合併管理TDataSet物件,所以用TDataModule來發展商業物件,是一個很好的選擇,,,另外TDataModule 也可以包裝程成COM的物件,供其他語言使用。
但是我們常見到一些專案的開發,把全部TDataSet 擠在一個DataModule中,我覺得這不是一個很好的設計,理由如下﹕
沒有效率,當產生一個Instance 那是一個笨重的物件。
無法分割出所需要物件,以達成重複使用的目的。
資料物件全部存在一個DataModule內,以致相互糾纏,不容易維護。
所以,每個DataModule應該只放一個物件所需的TDataSet,當產生Instance時,可以克服以上的問題,這是以物件的觀點來看,可是當我們用資料的觀點來看時,還存在一些問題,在關聯式資料庫底下不同Table之間資料要如何關聯?我們第一個反應是使用TField的物件,用LookUp欄位來存取,可是當我們用LookUp去存取資料,可能必須去存去另一個DataModule的TDataSet,這樣還是一樣會把DataModul糾纏在一起 (如下圖)。
針對持續層資料存取的問題,以下提出兩個解決方案﹕
使用fkCalculated,把關聯式的關聯資料當成一個計算欄位來存取,而不要使用fkLookup,當然這無法在設計期間看到資料的結果。而另一方面使用fkCalculated必須在TDataSet 的 OnCalcFields 寫入控制的程式碼,OnCalcFields 事件在很多時候都會觸發(資料欄位改變,cursor 移動...),所以如果在OnCalcFields 中計算欄位值會拖慢程式回應時間,這是無法避免的一個缺點,而使用fkLookup 也一樣無法避免這個問題。
Delphi 的TField 的物件,是連接到實體資料的功臣,也是讓我們可以輕易的連結資料的關鍵物件。TField 提供四種資料連結模式﹕
fkData Field represents a physical field in a database table ﹕根據資料表的欄位取代現有欄位
fkCalculated Field is calculated in an OnCalcFields event handler﹕在執行期間由資料集的OnCalcFields Event 處理程序計算的數值
fkLookup Field is a lookup field.﹕執行期間根據所指定的條件從特定的資料集取得數值
fkInternalCalc Field is calculated but values are stored in the dataset.﹕顯示執行期間,由用戶端資料集計算的數值。與fkCalculated Field 不同的是fkInternalCalc Field 欄位計算的數值是儲存在資料集中,並可以像用戶端本身的資料一樣來存取。
fkAggregate Field represents a maintained aggregate in a client dataset.﹕顯示一組記錄內資料的匯總值。
其中fkInternalCalc及fkAggregate 兩種模式,只能在TClienTDataSet使用。
使用View 這個虛擬的Table 事先將資料連結起來,透過TClientDataSet進行連結,TClienTDataSet具有特異功能,可以一次更新多個檔案,在資料寫回資料庫時,也可以透過TDataSetProvider的設定,決定是否要由TDataSet接手處理。如下圖所示﹕
以上兩種解決方案,很顯然第二個方案,在資料存取方面,具有很大的彈性。TClienTDataSet 在寫回資料時擁有自己的方法,因此可以針對不同TDataSet進行操作,也就是可以面對不同資料庫。同時利用資料虛擬層,去穩定實體層的變動性,虛擬層可以用資料庫的View 來設計,由於View 可以跨越不同Table ,同時變成標準介面與DataModule結合在一起 ,所以之前所說LoopUp 欄位問題也就迎刃而解。
使用ADO資料庫引擎去連結資料庫時,TClientDataSet 有一部份的資料搜尋功能無法正常使用,包括Locate ,LoopUp ...等方法 , 這是由於TClientDataSet 無法處理UniCode ,所以中文字碼,無法搜尋。
(三) 資料庫與物件的對應關係
在傳統的系統分析方法,我們是以資料流配合關聯式資料庫進行系統分析,當資料規格設計完成,系統就配合使用流程及資料規格進行設計,沒有以物件為中心的觀點進行分析,將很難把物件對應到實體資料。所以以下列出幾個必須改變的觀念。
先建立物件,以物件觀點進行系統設計,找出系統中所要的物件。
資料要配合物件來建構,所有資料是由物件控制,不是由流程控制或輸出入表單控制。
資料表的設計,不要直接回應使用介面(UI),應透過物件來回應。
( 註: 建立以物件為中心的設計系統觀點 )
以商業物件對應關聯式資料庫,不外乎三幾種關係﹕
一對一關係
一對多關係
沒有對應關係
一個客戶物件,可能對應一個Table,一個異動單據可能對應一個Master-Detail Table,一個系統使用者有可能沒有對應的Table,所以不能用關聯式資料庫去反推對應到物件,而是必須將過分析,確實找出系統物件,以物件的觀點來建構資料庫。在使用需求的捕捉方面,使用UML(統一模塑語言) 是一個不錯的選擇,市場上也有許多支援的工具可供使用。
以物件為思考的系統,實體的資料主要是配合物件做資料存取,而非針對輸出入的資料去回應,因此再系統分析時,如何找出適當的物件,是一個很重要的課題。許多傳統的系統分析仍停留在以資料流為中心的想法,這種觀念無助於開發商業物件。
(四) 物件類別的設計
商業物件可以從TDataModule 繼承下來,先產生一個TMasterObject,將各物件共同的屬性及方法設計在這個物件,再從這個物件,繼承產生各類物件,如下圖所示﹕
介面設計時,我們仍可以在設計期間從各類別透過TField取得資料,享受Delphi 的視覺化的設計優點,而各類別之間的合作,只有在執行期間,再進行結合,譬如一個單據類別,可能需要傳送訊息給庫存類別,那麼可以在資料Post 回資料庫之前,傳送一個訊息給庫存物件,由庫存物件負責更新庫存,讓物件之間的責任清楚,系統會更有條理。
Class ( 類別 ) 是靜態的結構,當我們依照Class 建構一個物件時,產生一個Instance( 執行案例 ),物件與物件之間的合作關係,是在產生執行案例之後,不可以將Class 及Instance 兩者觀念搞混。
當我們從Delphi 的元件盤取產生一個元件,這個元件是一個Class ,只不過RAD會自動幫我們去建構物件,產生執行案例,以致讓使用者誤以為Class等於Instance。RAD隱藏了許多複雜的動作,讓我們易於使用,但也同時混淆了我們使用物件的觀念。
在面對不同的專案時,可能要進一步分析,究竟是個別獨立設計一個架構,或者是在同一架構中,改寫部分的物件功能,以達到重複使用的目的,這部分有賴於使用analysis pattern做進一步的分析。
(五) 結語
從理論到實作這一條路,存在有一些迷失,這個迷失其實也是上述所談的,有些物件觀念不夠清晰,碰上實作時,不知如何去應對問題。另一方面,在實作時,我們也才真正認識,到底是那些觀念不夠清楚,所以我強烈的建議各位找一個簡單的Case 去試試看,把所學的物件理論,去套用看看,問題馬上浮現出來。到目前為止 ,很少有人談論到Delphi 在商業物件實作技術上的問題,這不代表沒有問題,而有可能根本不知道問題在哪裡,而只是沉迷在RAD的快速設計,一方面享受,也一方面受害。
RAD並沒有害我們陷入困境,只是我們還停留在程序性的程式設計思考框框而不自知,這也是因為我們物件觀念不夠清晰,而RAD正考驗著我們的物件觀念是否夠堅強,用RAD但不要被拖著走,這也是我想說的一句話。