Delphi程式設計之--慣用法 (轉)

amyz發表於2007-11-07
Delphi程式設計之--慣用法 (轉)[@more@]

--技巧探索: 
{ No. 1 }
建立窗體的句子:
class procedure TMyForm.RunForm(AObj1, AObj2: T);
var
vForm: TMyForm;
begin
vFo:= TMyForm.Create(Application);
with vForm do
Try
 InitForm(AObj1, AObj2);
 ShowModal;
Finally
 Free;
end;
end;
//*說明:
透過class宣告的,類似與VC中的靜態函式;使用語句:TMyForm.RunForm(vObj1, vObj2);
其他具體的,參考:Delphi 幫助中的,class 類說明。
強調這個慣用法,就是為了:
1、如果此窗體在多處被使用,那麼可以保證統一都此段程式碼;
2、如果功能上有所修改,比如:根據ShowModal的返回值不同進行處理,那麼只修改此函式就行了。
3、封裝性好,易於維護和工作交接。*//

{ No. 2 }//Tag 的使用
窗體工具欄按鈕事件的響應
procedure TMyForm.RunOperate(ATag: Integer);
begin
 Case ATag of
 1: MyButton.Color := clRed;
 2: MyButton.Color := clGreen;
 3: MyButton.Color := clBlack;
 end;
end;

procedure TMyForm.ToolBtnClick(Sender: TObject);
begin
 RunOperate(TControl(Sender).Tag);
end;

如果你在某下拉選單中,也需要類似功能則
procedure TMyForm.MenuItemClick(Sender: TObject);
begin
 RunOperate(TMenuItem(Sender).Tag);
end;

//*說明:
1、結構清晰
2、相關的資訊集中,比較容易查錯、修改和維護
3、提高程式的適應、擴充套件能力;比如現在要求不在工具欄按鈕中實現,而要求在不同按鈕中實現,則修改容易。
建議:每個分類後面只跟一行或不多的幾行程式碼,如果程式碼比較多,使用過程函式替代。
比較有意思的是,我經常如下寫:
Case btnMyButton.Visible of
{ 顯示 } True: ...
{不顯示} False: ...
end;  *//

 

{ No. 3 }//事件指標做引數
//對於列表等的讀取使用事件指標的方式
type
 TDataSetEvent = procedure (DataSet: TDataSet; AIndex, ACount: Integer) of Object;
//從 TADOQuery派生而來的類
procedure TMyADOQuery.EnumRecord(AWhereStr: String; APro: TDataSetEvent);
begin
 Close;
 .Clear;
 SQL.Add(' * From Table1');
 if AWhereStr <> '' then
   SQL.Add('Where ' + AWhereStr);
 Open;
 While Not Eof do
 begin
   if Assigned(APro) then APro(Self, RecNo, RecordCount);
   Next;
 end;  
 Close;
end;

//*說明:
此方法來自與Window中,列舉當前所有子窗體的函式,EnumChildWindow
1、原則:儘量將資料讀取與資料顯示、資料處理等分離;如:MVC等都是此目的。
2、程式擴充套件性增強,如果您原來希望在列表中顯示或處理某列資訊,後來改為用ComboBox,則在修改程式時,不在閱讀資料讀取部分,只需要修改資訊顯示等即可。又比如,現在要求您在讀取記錄時,用進度條顯示讀取進度等。
*//

 

{ No. 4 }//常量陣列

{ 在 No.2 中,實現瞭如下的內容
procedure TMyForm.RunOperate(ATag: Integer);
begin
Case ATag of
1: MyButton.Color := clRed;
2: MyButton.Color := clGreen;
3: MyButton.Color := clBlack;
end;
end;
}
//那麼用陣列方式實現,則就比較理想了
procedure TMyForm.RunOperate(ATag: Integer);
const
 MyButtonColorMax := 3;
 MyButtonColor: array [1..MyButtonColorMax] of TColor = (clRed, clGreen, clBlack);
begin
Case ATag of
1..MyButtonColorMax:  MyButton.Color := MyButtonColor[ATag];
101:....
end;
end;

//*說明:
對於陣列方式使用,只要注意陣列的上限或下限使用常量來實現,然後在以後使用中都儘量使用此常量進行陣列迴圈讀取就行了。
*//

 

{ No. 5 }訊息機制 減少類公共函式
//如何讓一個窗體中,儘量減少公共函式的定義;
{ 比如:要實現一個當前窗體的屬性列表窗體,當需要重新整理屬性窗體;改變某屬性值;新增新的屬性等;會有很多需要互動的資訊。如果我們使用類公共函式,則需要定義很多的公共函式。同時,如果需要進行窗體型別轉換,轉換為目標窗體型別才可以使用公共函式。所以,會遇到兩個單元需要互相包含的情況 }
//解決方案:
TfrmMyForm = class(TForm)
 FfrmProperty: TForm;
end;

...
 FfrmProperty := TfrmProperty.MyCreate(Application, Self);
...

//當需要重新整理屬性窗體時
 FfrmProperty.Perform(WD_REFRESHPROPERTYLIST, 0, 0);

TfrmProperty = class(TForm)
private
 FMyForm: TForm;
 procedure WDREFRESHPROPERTYLIST(var Message: TMessage); message WD_REFRESHPROPERTYLIST;
public
 constructor MyCreate(Owner: TComponent; AForm: TForm);
end;

constructor TfrmProperty.MyCreate(Owner: TComponent; AForm: TForm);
begin
 inherited Create(Owner);
 FMyForm := AForm;
end;

//* 對於使用訊息的方式,可以減少窗體公共函式的定義。同時,提高程式的可擴充性。如果,使用他的窗體替代時,則可以比較輕鬆的轉換,因為如果最多也就是您的窗體,對當前的訊息沒有進行處理而已 *)// 

 

 

{ No. 6 }使用註冊列表管理可能擴充的模組
//專案:要求你對一個資料集支援多種輸出顯示方式

...例子,以後給出

//* 說明:
1、“多種輸出方式”,說明輸出方式在今後的應用中可能會經常擴充,因此要在時考慮到輸出方式的易擴充性。
2、參考VCL中,控制元件註冊(RegisterComponents)的機制,可以發現VCL中大量的使用到了序號產生器制;其中比較經典的就是控制元件屬性編輯器的註冊了。
*//

{ No. 7 }使用預定義控制程式版本

//如果您做的是一個二次開發平臺的程式,則必須涉及到產品版本控制和專案版本控制問題
//通常使用預定義的方式控制

//語句比較簡單了就是:
{$DEFINE JOYYUAN97}
{$IFDEF JOYYUAN97} {ELSE} {ENDIF}
{$UNDEF JOYYUAN97}

*說明:
1、將預定義劃分在多個單獨的中。
2、在每個單元的最前頭但在Unit 後,使用{$I ...} 將檔案包含(Include)進當前單元
3、根據預定義情況控制當前單元所能包含的單元檔案
4、儘量單獨劃分一個針對專案的預定義檔案在包含所有預定義檔案後,包含此檔案,則在此檔案中,可以針對專案的需要,將取消部分預定義{$UNDEF JOYYUAN97}
*//

{ No. 8 } 使用函式指標,減少單元專案包含

//我經常的認為減少單元的包含,是做公共單元的第一步,所以在如何儘量減少單元包含
//也就是如何減少程式單元的耦合性上,應多下工夫。

{ 情景描述:
TMyFormManager: 窗體管理類
TMyForm:資料窗體基礎類
TMyFormAccess:窗體資訊儲存和讀取類。將窗體資訊儲存到或其他什麼型別的結構中
分析:
1、窗體基礎類(TMyForm) 和 窗體管理類(TMyFormManager)需要在一個單元 uManagers中實現。
2、窗體具體實現類(TMyImageForm)單元 fMyImange 需要包含單元uManagers,進行窗體繼承,和窗體管理。
3、窗體資料讀取類(TMyFormAccess)單元 uMyAccess 需要包含單元uManagers和單元fMyImange
問題:
 如果我希望實現窗體儲存,那麼應該在窗體的某個按鈕事件中實現。則涉及到窗體單元需要包含窗體資料訪問類單元,而如果放在窗體基礎類中,則單元uManager又必須包含單元uMyAccess。
 當資料訪問,即資料格式會根據要求而改變並要求可擴充時,則單元包含必定是一個隱患。
解決辦法:使用函式指標變數。
1、在單元uManagers中定義一個,儲存資料資訊的函式指標變數。
2、在應用程式初始化的時候給這個函式指標變數賦值。
3、在需要儲存窗體資訊時,判斷如果指標不為空,則執行函式儲存窗體資訊。

{ No. 9 } 常量,認識常量,使用常量
有很多書都都介紹了常量定義的重要性,我也會經常想到,但是看看VCL原始碼才知道,自己忽略了,別人對常量的使用情況。

1、我們經常使用的訊息的定義就是:宣告一個常量,然後在適當的時候使用之。
 通常定義和使用:
 const
   WD_MyMessage = WM_User + 101;
 type
 TMyForm = class(TForm)
 ...
   procedure WDMyMessage(var message: TMessage); message WD_MyMessage; {響應訊息位置}
 end;
 但是,如果您將{響應訊息位置}語句改寫為:
   procedure WDMyMessage(var message: TMessage); message WM_User + 101;
 同樣,編譯可以成功,使用也正常。所以,常量定義在Window處理和介面中應用非常普遍。

2、在Delphi中,我們定義了顏色變數,clRed, clGreen等,也都是定義的常量,便於以後的使用。透過這個觀察我發現,常量的定義應該是在專案中,可部分複用的,所以,可以定義一個標準常量單元,以便在個專案中,複用定義的常量。

 

 { No. 10 }一個Delphi中,常用到的陣列

對TntMapEntryd型別的陣列定義和使用,Delphi中,有比較完善的實現。
 TIdentMapEntry = record
   Value: Integer;
   Name: String;
 end;

1、陣列定義:array[0..ArrMax] of TIdentMapEntry
 可參考:Controls單元中:
 Cursors: array[0..21] of TIdentMapEntry = (
 ...
 );
2、兩個互相求值得函式: IntToIdent(由Value求Name)和 IdentToInt(由Name求Value);
 具體應用可以參考:IdentToCursor 和 CursorToIdent。

3、應用:a、直接應用此樹組定義方式和陣列操縱函式;b、學習函式中,對陣列訪問和操縱的方式。c、學習標準的資訊訪問函式定義: function IntToIdent(Int: Longint; var Ident: string; const Map: array of TIdentMapEntry): Boolean; 具體返回的資訊由引數方式返回回來,至於訪問是否有效,則透過函式的布林返回值加以判斷。

 

{ No. 11 } 由特例到普通的發現
我透過對 Cursors 的定義和操作函式的跟蹤發現:
1、如 { No. 10 }中介紹的,將Cursors的定義和一般操作通用化。
2、提供 Int 和 Ident互轉化的函式。
3、提供陣列列表資訊循讀取的函式: GetCursorValues;其中,使用了 { No. 3 } 中介紹的“事件指標 做引數”讀取列表資訊的方法。

{ No. 6 } 的補充:
例子:
procedure RegisterComponents(const Page: string;
 ComponentClasses: array of TComponentClass);
begin
 if Assigned(RegisterComponentsProc) then
   RegisterComponentsProc(Page, ComponentClasses)
 else
   raise EComponentError.CreateRes(@SRegisterError);
end;

解讀:
1、使用註冊的方式,記錄可使用的控制元件的型別等。
3、對於 RegisterComponentsProc 使用了{ No. 8 } 中“使用函式指標,減少單元專案包含”的方法,便於將來程式的擴充,版本的升級等。

 

 { No. 11 }只定義一個公共函式
//專案描述:現在要實現一個CAD畫圖或Visio系統,要求有好的擴充套件性和易維護性;
//並且要求耦合性低,便於,將來系統的部分或擴充套件後的系統封裝後,直接在今後的專案中使用

設計:
1、設計一個圖形抽象類,在此類中,定義一個抽象函式 CadPerform,函式的引數參照function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;
2、在圖形管理類中,實現一個圖形物件列表的管理,列表中儲存的是抽象物件的指標。
3、對於要對具體類物件進行操縱控制時,只需透過條用CanPerform函式,然後根據當前操作的類別傳入 Msg, 並傳入相應的引數資訊。

實現: TCad 為由抽象類繼承下來的第一層控制元件類
function TCad.CadPerform(Msg: Cardinal; WParam, LParam: Longint): Longint;
begin
 Case Msg of
 My_Message1: Result := MyMessage1(WParam, LParam);
 My_Message2: Result := MyMessage2(WParam, LParam);
 end;
end;
對於,TPoint繼承自 TCad, CadPerform函式實現如下。
function TPoint.CadPerform(Msg: Cardinal; WParam, LParam: Longint): Longint;
begin
 Case Msg of
 My_Message1: Result := MyMessage1(WParam, LParam); //遮蔽了TCad中此操作型別的處理
 My_Message3: Result := MyMessage3(WParam, LParam);
 else Result := inherited CadPerform(Msg, WParam, LParam);
 end;
end;

*說明:
因為,我們對圖形物件的操作會非常頻繁,所以我們透過定義一個公共開放的介面函式來實現,類的高封裝性和程式的易維護性、好擴充套件等。
*//

 

{ No. 12 }

以下是我時的要求:(部分資訊沒有語言限制)
//以下的解決方案,幾乎都可以在上面的方法中,找到
1、減少程式的複雜度。a、減少函式個數,使用Case、Tag方式,學習實現Perform定義方式;b、減少單元巢狀關係,使用訊息傳遞方式,減少窗體單元的互相包含。
2、減少

 

{ No. 13 }使用廣播,實現管理類對管理列表物件的通知

//對於{ No. 12 } 專案描述中,當畫圖的窗體控制元件屬性或狀態改變時,經常會需要通知所有的圖形物件,進行相應的改變。
//則如果只定義一個廣播函式,就可以實現父子通知的話,也會提高程式的可重用性、擴充套件性、易維護性等,使類結構清晰。

//比如:1、在Visio和MapInfo中,如果當前窗體的比例尺(縮放比例)改變時,需要用新的比例尺重畫當前所有的顯示圖形物件。2、噹噹前窗體預設窗體字型改變後,對於預設使用窗體字型顯示文字資訊的圖形物件,他們的文字字型也應該相應的改變。

//解決方案,參考TWinControl中,屬性或狀態改變時,通知所有子Controls的處理機制:
procedure TWinControl.NotifyControls(Msg: );
var
 Message: TMessage;
begin
 Message.Msg := Msg;
 Message.WParam := 0;
 Message.LParam := 0;
 Message.Result := 0;
 Broadcast(Message);//廣播當前的變更訊息
end;
其中:
procedure TWinControl.Broadcast(var Message);
var
 I: Integer;
begin
 for I := 0 to ControlCount - 1 do
 begin
   Controls[I].WindowProc(TMessage(Message));
//改為:with TMessage(Message) do Cads[I].CadPerform(msg, WParam, LParam);
   if TMessage(Message).Result <> 0 then Exit;
 end;
end;
但是,我們處理圖形物件時,可能會直接呼叫 Cads 的CanPerform公共函式即可

{ No. 14 }需要時,動態建立你的物件

比如:ASP?xid=824" target=_blank>http://www.delphibbs.com/keylife/iblog_show.asp?xid=824 中的
//*******方案二  當需要的時候在建立屬性窗體
uses
...
fProperty;
type
TfrmMyMap = class
  ...
  procedure OnfrmMyMapDestroy(Sender: TObject);
  procedure OnMapGeoSelected(AGeo: TGeometry);
private
  FfrmProperty: TfrmProperty;
  procedure ShowPropertyForm(aVisible: Boolean);
public
end;

procedure TfrmMyMap.ShowPropertyForm(aVisible: Boolean);
begin
if Not Assigned(FfrmProperty) then FfrmProperty := TfrmProperty.Create(Application);
FfrmProperty.Visible := aVisible;
end;

procedure TfrmMyMap.OnfrmMyMapDestroy(Sender: TObject);
begin
if Assigned(FfrmProperty) then FfrmProperty.Free;
end;

procedure TfrmMyMap.OnMapGeoSelected(AGeo: TGeometry);
begin
if Assigned(FfrmProperty) then FfrmProperty.MyRefresh(AGeo);
end;

這裡說明了:
1、需要時,動態建立你的物件 FfrmProperty
2、當前物件釋放時,判斷你的物件的合法性,然後釋放動態建立的物件。

 

 { No. 15 }建立介面還是建立結構

//專案描述:我開發一個表格控制元件時,如果我將單元格設定為一個Com,則如果表格現實的資訊過多的話,則裝載速度無法保證,甚至於有當機的可能。我之所以用Com是為了將來每個單元格的處理和資訊都可以在控制元件外擴充套件。

我的解決辦法是:對於每個從Cell派生來的控制元件建立一個例項,透過動態建立若干個結構物件Record來記錄個單元格的資訊,如果需要對單元格進行操作,則將結構物件指標賦值給Cell,測試結果很令人滿意。

所以,如果需要使用某個Com大量例項的話,儘量管理和維護一個例項,而對於其中的資料可以實行動態建立管理,速度上會有很好的效果。
另外,儘量宣告一個 pMyInterface = ^IMyInterface 藉口指標,引數傳遞或使用時,直接使用介面指標,這樣可以減少呼叫計數函式_AddInft等,如果操作平凡也可以提高速度的。

注:此文作者筆名:JoyYuan97。(自己很菜,寫不出來什麼,找到好的東西希望大家一起分享,也感謝作者給我帶來的和技巧)

希望看過的人留下意見,下面為作者的話:
“另外,最好請將您轉貼位置的連線給我一份。我向定期看看別人的建議。謝謝!
有好經驗大家分享!”


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

相關文章