程式碼重構-之獲得封裝性DELPHI編碼實例程式碼重構是獲得結構良好的方法,透過重構,我們在保持功能不變的情況下,改善程式碼的質量,提高程式碼的複用程度。下面是一個獲得改善程式碼品質和獲得封裝性的一個具體的例子。 (範例使用DELPHI)程式碼功能: 給資料集設(TClientDataSet)置過濾器,使用者可以在一個TComboBox中選擇要過濾的字段,然後在一個Tedit框中輸入要過濾的值。如圖一:最常見的做法就是在TComboBox的Items屬性中硬碼寫入我們資料集中的欄位名稱,然後在程式碼中加入一大堆case或if語句在判斷使用者選擇的欄位來為資料集設定過濾器。 …… case ComboBox1.ItemIndex of0: ClientDataSet.Filtered := False; ClientDataSet.Filter := ' F_CODE = ''' + Edit2.Text + ''''; ClientDataSet.Filtered := True;1: ClientDataSet.Filtered :=Set.Filtered :=Set.Filtered :=Set.Filtered :=Set.Filtered :=Set. False; ClientDataSet.Filter := ' F_CHINESE_NAME = ''' + Edit2.Text + ''''; ClientDataSet.Filtered := 真;…… end; 或用….… if ComboBox1.Text = '物料編碼' then begin ClientDataSet.Filtered := False; ClientDataSet.Filter := ' F_CODEDE.Filtered := False; ClientDataSet.Filter := ' F_CODEDE = ''' + Edit2.Text + ''''; ClientDataSet.Filtered := True;endelse if ComboBox1.Text = '姓名' thenbegin ClientDataSet.Filtered := False; ClientDataSet.Filter := ' F_CHINESE_NAME = ''' + Edit2.Text + ''''; ClientDataSet.Filtered := True;end…這樣的程式碼透過硬碼同樣也實現了這個給資料集設定過濾器的功能,滿足了需求,但是上面這段程式碼是不靈活的。如果資料集的欄位很多就要求編碼人員一個一個欄位輸入在Items中,而且在寫case必須核對好順序,不然設定的過濾器就是錯誤的也就很容易由開發人員引入BUG。用if語句時也一樣維護一個大量的if同樣是痛苦的,而且不支援需求變化,當使用者要求改變資料集欄位的中文顯示名稱時必須也要記住更改TComboBox. Items中的硬碼數據,如果一旦忘記就會引入BUG。於是我在第一次重構中,嘗試動態的載入TComboBox. Items中的數據,同時為了實現載入後使用者選擇時實作對照。我在這個查詢FORM中加了一個私有FFields: array[0..20, 0..2] of string; 欄位來保存資料集中的欄位資訊資料。同時實作了載入資料的過程:PRocedure TFrmSPARealStorageQuery.GetQueryFields;var i, iFieldsCount: Integer;begin iFieldsCount := 0; with DBGride1.DataSource.DataSet do begin for i := 0 to Fields. - 1. i].Visible then begin FFields[iFieldsCount, 0] := Fields[i].FieldName; FFields[iFieldsCount, 1] := Fields[i].DisplayLabel; Inc(iFieldsCount); end; ComboBox1.Items.Clear; for i :iel 0 to iFCount); - 1 do ComboBox1.Items.Add(FFields[i, 1]); end;end;這樣就實現了在運行時動態載入字段資訊。這樣我的過濾器設定就變成這樣的了。 if ComboBox1.Text <> '' thenbeginClientDataSet.Filtered := False; ClientDataSet.Filter := FFields[ComboBox1.ItemIndex, 0] + '''' + Edit2.Text + ''''; ClientDataSet.Filtered :=Set.Filtered := True;end;本方法無疑增加了程式碼的靈活性,同時增加了程式碼的複用度,因為程式碼很好的隔離了變化的資料。因此只要在另一個也是要實現這種的功能的FORM中增加私有字段FFields: array[0..20, 0..2] of string 和使用上面的動態加載數據集字段過程,就可以說方便的實現了重用。但是這種重用並不是很好的,因為我們沒有實現很好的封裝性。導致在你的程式中到處散落有重複的程式碼(你常常會透過COPY來獲得這個函數的重用,因為上面的程式碼是沒有好的封裝性)。如果有一天你要修改資料裝載函數你就必須到處去找那裡拷貝了這個函數-你也得修改散落在其他地方的程式碼。於是我進行了再一次的重構,並對程式碼進行了進一步的封裝。程式碼如下:unit uDataSetFieldsInfo;// Description:單元包含TDataSetFieldsInfo 類,該類別封裝了取得資料集子段資訊。 // 並提供了在combobox清單顯示欄位中顯示資訊和取得對應子段名稱的方法介面// Created : wuchhao// Date : 2003.5interfaceuses Classes, DBClient, StdCtrls;type TDataSetFieldsInfo = class private FFielList: Creorate;type TDataSetFieldsInfo = class private FFielList: Creorate; ; destructor Destroy; override; procedure GetDataSetFields(Source: TClientDataSet); procedure ShowFieldsInfo(Target: TComboBox); 函數 GetFieldsNameByDisplayLabel(DisplayLabel: string): string; end;implementation{ T TStringList.Create;end;destructor TDataSetFieldsInfo.Destroy;begin FFieldsList.Free; inherited;end;procedure TDataSetFieldsInfo.GetDataSetFields(Source: TClientDataSet);var i: Integer;begin for FFielList.Clear ; Fields.Count - 1 do if Fields[i].Visible then begin FFieldsList.Add(Fields[i].DisplayLabel); FFieldsList.Add(Fields[i].FieldName); end; end;end;function TDataSetFieldsInfo.GetFieldsNameByDisplayel(ByDisplayel ( var index: Integer;begin Result := ''; index := FFieldsList.IndexOf(DisplayLabel); if index <> -1 then Result := FFieldsList.Strings[index+1] ;end;procedure TDataSetFieldsInfo.ShowFieldsInfo(Target: TComBox);var i: IntBox); ;begin Target.Items.Clear; i:=0; while i < FFieldsList.Count do begin Target.Items.Add(FFieldsList.Strings[i]); i:= i+ 2; end;end;end.單元uDataSetFieldsInfo 封裝了與實現本文所述功能相關的資料和方法,把它們封裝在一個類別裡面,從而實現了物件導向設計裡面的Open - Close 原則。類別變成了黑盒,於是就可方便的重用(black-box reuse),而不必擔心程式碼的重複。同時因為封裝了與功能相關的訊息,類別的職責定義明確(單職責),並有了足夠合適的粒度和好的封裝性。 TdataSetFieldsInfo 很好的把組合框與變化的資料隔離開來,最終提高了程式碼的複用程度,同時減少了FORM類別的職責和magic number硬編碼的量。下面是新的程式碼:首先在FORM中聲明TdataSetFieldsInfo類別的一個參考。 ……在FORM創建的時候調用:FFieldsInfo := TDataSetFieldsInfo.Create;FFieldsInfo.GetDataSetFields(cdMaster);FFieldsInfo.ShowFieldsInfo(ComboBox1);這時候我的過濾器設定就變成了:if ComboBox1.Text <Text> '' thenbeginClientDataSet.Filtered := False; ClientDataSet.Filter := FFieldsInfo.GetFieldsNameByDisplayLabel(ComboBox1.Text) + '''' + Edit2.Text + ''''; ClientDataSet.Filtered := True;end;透過呼叫FfieldsInfo物件的介面程序來取得對應的子段名稱。本文就是一個重構程式碼的簡單例子,我想上面我實作的這個類別還可以有很多種寫法和更好的演算法。這裡只是提供一個關於重構程式碼的思路,為提高我們的編寫程式碼品質和它的可維護性、擴充性,探討OOD程式設計方式上的思路。