一.實作商業物件封裝了商業規則的類別是真正物件導向程式設計的基礎這篇文章我們會涉及程式設計的各個方面,並對質疑一些我們寫Delphi程式的慣用方式。這些設計方法背後的基礎概念是封裝:設計一組清楚定義介面(方法)類,由這些方法去操作他們的屬性。這個概念將會貫串整個程序並對資料如何保存和呈現有很大影響。我願意介紹讀者學習Francis Glassborow's關於C++的文章,儘管語言不同,但是優秀的類別設計理念是和語言無關的。現今大部分Delphi寫的程式都不是物件導向的。只是語言中有物件的模型並使用了原有的或新的類,這並不意味著者程式是真正的物件導向。程式碼的重複使用隨著第三方控制被拖曳到視窗上而結束,視窗和單元之間的相互依賴卻在迅速擴散。 (!!!)如果將來要改變程式的基礎(如切換不同資料庫或從兩層結構變成三層結構)會嚴重受阻或花費昂貴。如果是真正以物件導向的方式寫的程式則會很方便而不是受限制。當然要寫這樣的程序需要理念的提升,而且開始的時候缺乏生產力,大部分開發團隊都不情願這樣或作考慮。我希望透過這篇文章向大家示範如何能寫出更好的程式。最終使系統更可靠,容易維護,風格一致,靈活,可重用,比用傳統方式寫的程式運行的更好。特別是對大型的程序,程式碼清晰且真正物件導向的程式會比傳統方式寫的同樣程序需要更少的維護資源。物件導向的程序更高的可靠性來自於資料和操作被封裝在明確定義的類別中。編譯器透過強大的型別檢查促使程式碼中正確的類別、方法和屬性,對未來一個改動會影響整個程式的程式碼不應讓人有誤解程式碼意圖的可能。正確使用類別會是類別之間的關係是自明的,大部分的程式碼真正關注程式的關鍵部分(meat),而不是考慮像資料如何持久儲存這樣的細節問題。貫串程式碼的簡單性和一致性將使程式的維護性顯著提高。正如我們將看到的一樣,廣泛使用類別繼承增加了生產力和可靠性,並且增強了一致性。這些一致性體現在所展示的程式碼中,包括類別的行為、資料如何儲存和使用者介面如何呈現資料。由於大部分功能在基類中提供,可以透過快速改變他們的行為來從根本上改變程式。 (如使用者互動介面從視窗驅動形式改為以html為基礎)這些基類可以設計為與程式無關,這樣第二個類似的程式將在生產力上立即得到推動。對一個中等的程序一組優秀的基類可以提供高達%50的顯著提升,從時間,開支到可靠性。首先要強調的是切換到真正的物件導向開發並非瑣碎小事,第一次應保證有豐富經驗的協助,或者程序較小且沒有緊急的交付期限;還要說明的是一個面向對象(OO)的解決方案並不是規定程式中哪些類別該用哪些類別不該用。如果一家公司開發了自己的視覺化控件,或使用第三方的視覺化控件,核心類別設計任何物件導向程式的第一步是考慮哪些必須的類別。這是絕對基本的一步,要有其他各種開發的技術保證,因為早期階段的錯誤要改正將花費昂貴。在設計我們的類別時我們一般努力實現低耦合高內聚-類別與類別之間盡量獨立,但又可以透過某種強大地方式複合。實現這一目標地一個方式是把類別按照在它們程序中所扮演地不同角色來劃分成不同地類別。對這些角色的正確選擇將會形成一組內聚的類別。在對類的角色設計中有一個貫串始終的基本原則:把類別的責任分為表現、應用和持久化儲存資料(典型地在資料庫中)。雖然這和三層資料庫程式地劃分是一樣的,要提示的是這種劃分的概念可以在多種環境中實現:從單晶片程式到分散式多層程式。組成應用邏輯的這一組類別負責最困難的工作,例如回應使用者操作和處理資料的請求。這一層類別中有一部分類別表現了真實世界的實體,並被系統所模型化。這些類別常被稱為「商業類」或「問題域類別」。它們構成任何物件導向程式至關重要的部分,因為其它的類別將透過某種方式支援這些類別,它們成為所有開發者關注的焦點。辨識一個特定程序中有哪些商業對像一般是經驗的本能,雖然這個過程裡面有一個完整的學科(或是藝術?)面向對象與傳統技術如SSADM(1)相比的優點貫串於整個分析設計和維持實體的過程:可以透過物件導向分析(OOA)、物件導向設計(OOD)和物件導向程式設計(OOP)來表現每個商業物件。我們會在後面探尋辨識合適的商業對象的部分技巧。首先我們假設下面這些過程已經完成。不同層(表現、應用和持久)間的類別的互通已清楚定義並且相互連結。這些類別的關係如圖1圖中的箭頭顯示同一個層中的類別可以呼叫另一個類別中的方法。這張圖同時說明表現層(使用者介面)的類別可以操作應用層或使用者介面中的其他類,應用層(商業物件)可以操作應用層的類別和呼叫持久層的方法,持久層只回應應用層的請求。商業對象為了示範商業對像是如何實現的,我們接下來將看到一個簡化的程序,其中有庫存,客戶和訂單實體(某個公司提供一定數量的貨物,客戶來買這些貨物)。我們最初的分析確定需要三個商業對象來表現這些實體。在我們的Delphi程式中將有三個類別:TStockItem, TCustomer 和Torder。清單1中顯示了這些類別的公共介面。這裡需要指出這些類別的特性是透過屬性(PRoperty)暴露給外部的:這是一個簡單而有益的類別設計並且透過屬性控制外部的存取。還有這些屬性應該在類別的published區域(原因會在後面提到),並被賦給適當的預設default值。雖然這些屬性限定只在流化類別時被用到,會給每個屬性合適的初始值並使程式碼更具自描述性。第一個類別框架在開發程式的初始階段,我們已經識別並實作了三個商業物件。這三個物件扮演共同的角色:透過某種方式表現真實世界的實體。因此我們希望它們和真實世界的實體有相似的屬性和適當的層次。我們將給每個物件一個共同的基類,叫做TPDObject(Problem Domain object)。隨著時間推移我們會在這個類別中加入公共的方法和屬性,TPDObject將會不斷擴展。要注意的是TPDObject不要(永遠不要)有任何跟特定程式相關的元素。作為一個程式無關的類別它將放在一個獨立的單元並形成框架的基礎:一組可以在許多程式中重複使用的類別。在後面我們的框架會有很大擴展,形成提供重要的程式無關功能的基類,並且可以迅速用於特定的系統。我們TStockItem、TCustomer 和Torder就是從TPDObject為特定係統客製化物件。 TMyAppPDObject是TPDObject的繼承類,雖然它的實作部分是空的,但是它是一個很好的示範;如果我們為在特定程式加某些特定元素,並影響程式中所有的問題域類,這樣的類層次應該是比較合適的。清單中列出了框架的初期程式碼。類別TPDObject也只提供一個唯讀屬性ID,它的型別是TobjectID。這個屬性給了每個物件一個識別符:我們將把相同類型和ID的兩個實例認為是同樣的物件。這裡ID被故意聲明為自己的類型是為了避免被直接賦給其它標準類型的值。 TobjectID的類型並沒有特別的選擇:在這裡我選擇了整形,在特定情況下它也可能是專門的類別。雖然用類別作ID好像是更純的面相對象,但基於我們的問題域類別在程式運行中會被創建、釋放上千次考慮,為了避免創建和釋放對象時額外的負荷,我並沒有這樣做(身為純粹主義論者我把TobjectID包含在TPDObject中作為複合的物件)。在程式碼中有一對函數用於轉換字串和我們的物件識別類型,並且有一個常數作為沒有賦值時的初始值。有很多課堂會提到物件識別:有的說所有的商業物件都應該在創建時被賦予一個程式內不重複的識別(甚至是GUID)。實際上,能在給定的上下文中區分不同的物件才是真正重要的,並且保持這一簡單性將會在效能和儲存空間上有明顯的好處。關於規範的問題:為了強化一些設計的理念和設計類別框架時的做法我將提一些問題,請讀者思考它們後面的基本原理是什麼。我曾提到對於真正的物件導向設計並不禁止使用特定的類,但有一個很重要的例外。這個例外是什麼(答案在圖1)((( Listing 1 - An application-specific Problem Domain unit (abridged) )))unit ProblemDomain;interfaceuses Framework;type TMyAppPDObject = class (TPDObject) end; TStockItemuses Framework;type TMyAppPDObject = class (TPDObject) end; TStockItemuses Framework;type TMyAppPDObject = class (TPDObject) end; TStockItemuses Framework;type TMyAppPDObject = class (TPDObject) end; TStockItemuses Framework;type TMyAppPDObject = class (TPDObject) end; TStockItemuses Framework;type TMyAppPDObject ) published property Name: String; property QuantityInStock: Cardinal default 0; property TradePrice: Currency; property RetailPrice: Currency; end; TCustomer = class (TMyAppPDObject) … ; TOrder = class (TMyAppPDObject) … ;implementationend.((( End Listing 1 )))(( An liing 2 - -independent Framework unit )))unit Framework;interfaceconst NotAssigned = 0;type TObjectID = type Integer; TPDObject = class private FID: TObjectID; public property ID: TObjectID read FID default NotAssigned; end;function StrToID (Str: String): TValueID: String): TStrID: Tunction : String;implementation…end.((( End Listing 2 )))(1) SSADM(Structured Systems Analysis & Systems Design)是英國政府的中央電腦及電訊中心(Central Computer and Telecommunications Agency ,簡稱CCTA ;網址:http://www.ccta.gov. uk )研發的軟體分析設計標準方法。 Philip Brown is a systems design consultant and active developer, presenter, and trainer. He'll promote the benefits of strong OO techniques to deliver better applications given any opportunity. You can contact him at [email protected].