前言
大多數Delphi程序員都像使用Visual Basic 那樣使用他們手頭上開發工具而絲毫沒有意識到Delphi的強大功能更談不上使用這些功能了(寫到這裡編輯惶恐的舉起了手怎麼可能呢?)Delphi和Visual Basic不同Delphi完全建立在面向對象結構上這不僅影響到VCL的結構而且影響到使用Delphi開發的每一個程序
在本文中我不想涉及到面向對象編程(OOP)的所有理論只是提出一些簡單的經驗規則希望這些規則能夠幫助改善你的程序結構無論你開發的是何種類型的程序這些經驗規則都是適用的你應當把他們當作一些建議記住他們並把他們應用到你開發的程序中去
關於面向對象編程我想強調的一個關鍵原理是封裝我們都希望創建一些靈活而且強健的類因為這樣的類允許我們以後修改他們的實現方法而不影響到程序中的其他部分這正是封裝給我們帶來的好處雖然封裝不是創建一個好的面向對象程序的唯一標准但是它構成了面向對象編程的基礎所以在本文中我也許會過多的強調封裝性請不要感到奇怪我有足夠充分的理由這麼做
最後我想說明這樣一個事實本文將主要集中說明窗體(Forms)的開發(雖然其中的一些規則對於組件的開發同樣適用)因此這些規則對於所有的Delphi程序員都是適用的那些編寫組件的程序員必須把面向對象編程和類(Class)作為核心的元素但是對於那些使用組件編程的程序員他們時常會忘記面向對象對於他們本文可以當作一個提示提醒他們始終記住面向對象編程
第一部分窗體是類(A Form is A Class)(rule rule )
程序員常常將窗體看作是對象而事實上窗體是類兩者的差別在於你創建基於相同的窗體類的多個窗體對象令人感到疑惑的是Delphi為你定義的每一個窗體類創建了一個默認的全局對象這對於新手來說是相當方便的但是這同樣會使他們形成壞習慣
第二部分繼承(Inheritance)(rule rule )
在講述了一系列關於類特別是關於窗體類的規則後第二部分將是一些關於類的繼承性以及可視化窗體繼承的建議和技巧
關於代碼
如果你想使用這些代碼請注意構造器必要的初始化設置以及私有組件參照同時有必要設置好窗體的OldCreateOrder屬性否則帶有組件的窗體構造器的初始化代碼將在窗體的OnCreate事件之前得到執行
規則一為每一個類創建一個單元(One ClassOne Unit)
請始終牢記這一點類的私有(private)和保護(protected)的部分只對於其他單元中的類和過程(procedure)才是隱藏的因此如果你想得到有效的封裝性你應該為每一個類使用一個不同的單元對於一些簡單的類比如那些繼承其他類的類你可以使用一個共享的單元不過共享同一個單元的類的數目是受到限制的不要在一個簡單的單元裡放置超過個復雜的類雖然Borland公司的VCL代碼曾經這樣做過
如果你使用窗體的時候Delphi會默認的遵循一個類使用一個單元的規則這對於程序員來說也是十分方便的當你向你的項目中添加一個沒有窗體的類時Delphi也會創建一個新的獨立的單元
規則二為組件命名(Name Components)
為每一個窗體和單元給出一個有意義的名字是十分重要的窗體和單元的名字必須是不同的不過我趨向於為他們兩者使用相似的名字如對於關於窗體和單元可以為他們使用AboutForm 和Aboutpas
為組件使用帶有描述性的名字同樣十分重要最常見的命名方式是使用類的小寫字母開頭再加上組件的功能如BtnAdd 或者editName采用這樣的命名方式為組件命名可能會有很多相似的名字而且也沒有一個最好的名字到底應該選擇那一個應該依據你的個人愛好而定
規則三為事件命名(Name Events)
對於事件處理方法給出合適的名字更加重要如果你對於組件給出了一個合適的名字那麼系統默認的名字ButtonClick將變成BtnAddClick雖然從這個名字中我們可以猜到這個事件處理程序的功能但是我認為使用一個能夠描述該方法的作用的名字而不是采用Delphi附加的名字是一種更好的方式例如BtnAdd按鈕的OnClick事件可以命名成AddToList這會使得你的程序可讀性更強特別是當你在這個類的其他方法中調用這個事件處理程序時而且這會幫助程序員為類似的事件或是不同的組件選用相同的方法不過我必須聲明使用動作(Actions)是目前開發重要的程序時我最喜歡的方法
規則四使用窗體方法(Use Form Methods)
窗體都是一些類因此窗體的代碼是以方法組織的你可以向窗體中添加事件處理程序這些處理程序完成一些特別的功能而且他們能被其他方法調用除了事件處理方法外你還可以向窗體添加完成動作的特別定義的方法以及訪問窗體狀態的方法在窗體中添加一些公共的(Public)方法供其他窗體調用要比其他窗體直接操作他的組件要好
規則添加窗體構造器(Add Form Constructors)
在運行時創建的第二個窗體除了一個默認的構造器(從Tcomponent 類繼承而來)外還會提供其他特殊的構造器如果你不需要考慮和Delphi以前的版本的兼容性問題我建議你重載(Overload)Create方法添加必要的初始化參數具體代碼可參見下面的代碼:
Public
Constructor Create(Text:string): reintroduce ; overload;
Constructor TformDialogCreate(Text:string);
Begin
Inherited Create(Application);
EditText:=Text;
End;
規則避免全局變量(Avoid Global Variables)
應該避免使用全局變量(就是那些在單元的interface 部分定義的變量)下面將會有一些建議幫助你如何去做
如果你需要為窗體存儲額外的數據你可以向窗體類中添加一些私有數據這種情況下每一個窗體實例都會有自己的數據副本你可以使用單元變量(在單元的implementation部分定義的變量)聲明那些供窗體類的多個實例共享的數據
如果你需要在不同類型的窗體之間共享數據你可以把他們定義在主窗體裡來實現共享或者使用一個全局變量使用方法或者是屬性來獲得數據
規則永遠不要在Tform類中使用Form(Never Use Form in Tform)
你應該避免在類的方法中使用一個特定的對象名稱換句話說你不應該在TForm類的方法中直接使用Form如果你確實需要使用當前的對象你可以使用Self關鍵字請牢記大多數時候你都沒有必要直接使用當前對象的方法和數據
如果你不遵循這條規則當你為一個窗體類創建多個實例的時候你會陷入麻煩當中
規則盡量避免在其他的窗體中使用Form(Seldom Use Form In Other Forms )
即使在其他窗體的代碼中你也應該盡量避免直接使用全局變量如Form定義一些局部變量或者私有域供其他窗體使用會比直接調用全局變量要好
例如程序的主窗體能夠為對話框定義一個私有域很顯然如果你計劃為一個派生窗體創建多個實例這條規則將是十分有用你可以在主窗體的代碼范圍內保持一份清單也可以更簡單地使用全局Sreen對象的窗體數組
規則移除Form(Remove Form)
事實上我的建議是在你的程序中移除Delphi自動創建的全局窗體對象即使你禁止了窗體的自動添加功能這也有可能是必要的因為在Delphi隨後仍然可能添加這樣的窗體我給你的建議是應該盡量避免使用全局窗體對象
我認為對於Delphi新手而言移除全局窗體對象是十分有用的這樣他們不至於對類和全局對象兩者的關系感到疑惑事實上在全局窗體對象被移除後所有與它有關的代碼都會產生錯誤
規則添加窗體屬性(Add Form Properties)
正如我已經提到過的當你需要為你的窗體添加數據時請添加一個私有域如果你需要訪問其他類的數據可以為你的窗體添加屬性使用這種方法你就能夠改變當前窗體的代碼和數據(包含在它的用戶界面中)而不必改變其他窗體或類的代碼
你還應該使用屬性或是方法來初始化派生窗體或是對話框或是訪問他們的最終狀態正如我前文所說的你應該使用構造器來完成初始化工作
規則顯示組件屬性(Expose Components Properties)
當你需要訪問其他窗體的狀態時你不應該直接訪問它的組件因為這樣會將其他窗體或其它類的代碼和用戶界面結合在一起而用戶界面往往是一個應用程序中最容易發生改變的部分最好的方法是為你需要訪問的組件屬性定義一個窗體屬性要實現這一點可以通過讀取組件狀態的Get方法和設置組件狀態的Set方法實現
假如你現在需要改變用戶界面用另外一個組件替換現有的組件那麼你只需做的是修改與這個組件屬性相關的Get方法和Set方法而不必查找修改所有引用這個組件的窗體和類的源碼詳細實現方法請參見下面的代碼
private
function GetText:String;
procedure SetText(const Value:String);
public
property Text:String;
read GetText write SetText;
function TformDialogGetText:String;
begin
Result:=EditText;
end;
procedure TformDialogSetText(const Value:String);
begin
EditText;=Value;
end;
規則屬性數組(Array Properties)
如果你需要處理窗體中的一系列變量你可以定義一個屬性數組如果這些變量是一些對於窗體很重要的信息你還可以把他們定義成窗體默認的屬性數組這樣你就可以直接使用SpecialForm[]來訪問他們的值了
下面的代碼顯示了如何將一個listbox組件的項目定義成窗體默認的屬性數組
type
TformDialog =class(TForm)
private
listItems:TlistBox;
function GetItems(Index:Integer):String;
procedure SetItems(Index:Integer:const Value:String);
public
property Items[Index:Integer]:string;
end;
function TFormDialogGetItems(Index:Integer):string;
begin
if Index >=ListItemsItemsCount then
raise ExceptionCreate(TformDialog:Out of Range);
Result:=ListItemsItems[Index];
end;
procedure TformDialogSetItems(Index:Integer;const alue:string);
begin
if Index >=ListItemsItemsCount then
raise ExceptionCreate(TformDialog:Out of Range);
ListItemsItems[Index]:=Value;
end;
規則使用屬性的附加作用(Use SideEffects In Properties)
請記住使用屬性而不是訪問全局變量(參見規則)的好處之一就是當你設置或者讀取屬性的值時你還可能有意想不到的收獲
例如你可以直接在窗體界面上拖拉組件設置多個屬性的值調用特殊方法立即改變多個組件的狀態或者撤銷一個事件(如果需要的話)等等
規則隱藏組件(Hide Components)
我經常聽見那些面向對象編程的狂熱追求者抱怨Delphi窗體中包含一些在published部分聲明的組件這是和面向對象思想的封裝性原理不相符合的他們確實提出了一個重要的議題但是他們中的大多數人都沒有意識到解決方法其實就在他們手邊完全不用重寫Delphi代碼也不用轉向其他語言
Delphi向窗體中添加的組件參照可以被移到private部分使得其他窗體不能訪問他們如果你這樣做你就有必要設置一些指向組件的窗體屬性(請參見規則)並且使用它們來訪問組件的狀態
Delphi將所有的這些組件都放在published部分這是因為使用這種方式能夠保證這些域一定是在DFM文件中創建的組件當你改變一個組件的名稱時VCL能夠自動地將這個組件對象與它在窗體中的參照關聯起來因為delphi使用RTTI和Tobject方法來實現這種功能所以如果想要使用這種自動實現功能就必須把參照放置在published部分(這也正是為什麼delphi將所有的組件都放在published部分的緣故)
如果你想知道的更詳細一點可以參看下面的代碼
procedure TcomponentSetReference(Enable:Boolean);
var
Field:^Tcomponent;
begin
If Fowner<> nil then begin
Field:=FownerFieldAddress(Fname);
If Field<>nil then
Field^:=Self
else
Field^:=nil;
end;
end;
上面的代碼是Tcomponent類的SetReference方法這個方法可以被InserComponentRemoveComponent和SetName等方法調用
當你理解了這一點後你應該不難想到如果你將組件參照從published部分移到了private段你將失去VCL的自動關聯功能為了解決這個問題你可以通過在窗體的OnCreate事件中添加如下代碼解決
Edit:=FindComponent(Edit) as Tedit;
你接下來應該做的就是在系統中注冊這些組件類當你為他們注冊過後就能使RTTI包含在編譯程序中並且能夠被系統所使用當你將這些類型的組件參照移到private部分時對於每一個組件類你只需為他們注冊一次即使為他們注冊不是一定必要的時候你也可以這樣做因為對於RegisterClasses的額外調用有益無害通常你應該在單元中負責生成窗體的初始化部分添加以下的代碼
RegisterClass([TEdit]);
規則面向對象編程的窗體向導(The OOP Form Wizard)
為每一個窗體的每一個組件重復上述兩個操作不僅十分的煩人而且相當的浪費時間為了避免額外的負擔我已經為此寫了一個簡單的向導程序這個程序將會生成一些可以完成以上兩步工作的代碼你需要做的僅僅是做幾次復制和粘貼就行了
遺憾的是這個向導程序不能自動將代碼放置到單元中合適的地方我目前正在修改這個向導程序希望能實現這個功能
規則可視化窗體繼承(Visual Form Inheritance)
如果應用得當這將是一個強大的工具根據我的經驗你所開發的項目越大越能體現它的價值在一個復雜的程序中你可以使用窗體的不同等級關系來處理一組相關窗體的多態性(polymorphism)
可視化窗體繼承允許你共享多個窗體的一些公共的動作你可以使用共享的方法公用的屬性甚至是事件處理程序組件組件屬性組件事件處理方法等等
規則限制保護域數據的使用(Limit Protected Data) 當創建一些具有不同分級體系的類時
一些程序員趨向於主要使用保護域
因為私有數據不能被子類訪問
我不能說這沒有其合理性
但是這肯定是和封裝性不相容和的
保護數據的實現能夠被所有繼承的窗體所共享
而且一旦這些數據的原始定義發生改變
你必須更改所有的相關部分
請注意
如果你遵循隱藏組件這樣一條規則(Rule
)
繼承窗體就不可能訪問基類的私有組件
在一個繼承窗體中
類似Edit
Text
=
的代碼就不會被編譯
雖然這是相當的不方便
但是至少在理論上這是值得肯定的事情
而不是否定的
如果你感覺到實現封裝性是最主要
最需要的
就請將這些組件參照放在基類的私有段
規則保護域中的訪問方法(Protected Access Methods)
在基類中將組件參照放置在私有域中而為這些組件添加一些訪問函數來得到他們的屬性這將是一種更好的方法如果這些訪問函數僅僅在這些類內部使用而且不是類接口的一部分你應該在保護域聲明他們例如Rule 中描述過的GetText和SetText方法就可以聲明成protected並且我們可以通過調用SetText()來編輯文本
事實上當一個方法被鏡像到一個屬性時我們可以簡單地采用如下代碼就可以達到編輯文本的目的Text:=;
規則保護域中的虛擬方法(Protected Virtual Methods)
實現一個靈活的分級制度的另一個關鍵點是定義一些你可以從外部類調用的虛擬方法來得到多態性如果這個方法使用得當將會很少出現其他公共的方法調用保護域中的虛擬方法的情況這是一個重要的技巧因為你可以定制派生類的虛擬方法來修改對象的動作
規則用於屬性的虛擬方法(Virtual Methods For Properties)
即使是訪問屬性的方法也能定義成virtual這樣派生類就能改變屬性的動作而不必重新定義他們雖然這種方法在VCL中很少使用但是它確實十分靈活強大為了實現這一點僅僅需要將Rule 當中的Get 和Set 方法定義成Virtual基類的代碼如下所示
type
TformDialog = class ( TForm)
Procedure FormCreate(Sender:Tobject);
Private
Edit:Tedit;
Protected
function GetText:String;virtual;
procedure SetText(const Value:String);virtual;
public
constructor Create(Text :String):reintroduce;overload;
property Text:String read GetText write SetText;
end;
在繼承窗體中你可以添加一些額外的動作來重載虛擬方法SetText
procedure TformInheritSetText(const Value:String);
begin
inherited SetText(Value);
if Value= then
ButtonEnabled:=False;
end;
小結
要做到一個好的Delphi面向對象編程程序員遠非我在上面提到的這些規則這麼簡單上面的這條規則中有一些可能需要足夠的耐性和時間來實現因此我也不能強求你能遵循所有的這些規則但是這些規則應該被合適的運用到你的程序中而且當你開發的應用程序越大參與的程序員越多這些規則越重要不過即使是一些小程序始終記住這些規則並在合適的地方使用他們也會對你有所幫助
當然還有很多其他的經驗規則我沒有涉及到特別是存儲器處理和RTTI問題因為他們十分的復雜需要專門的說明
我的結論是要遵循我上面列出的規則會付出一定的代價特別是額外的代碼但是這些代價會讓你得到一個更加靈活強壯的程序希望Delphi的後續版本能夠幫助我們減少這些代價
From:http://tw.wingwit.com/Article/program/Delphi/201311/8509.html