代碼重構是獲得結構良好的方法,通過重構,我們在保持功能不變的情況下,改善代碼的質量,提高代碼的復用程度。下面是一個獲得改善代碼質量和獲得封裝性的一個具體的例子。(例子使用Delphi)
代碼功能:
給數據集設(TClIEntDataSet)置過濾器,用戶可以在一個TComboBox中選擇要過濾的字段,然後在一個Tedit框中輸入要過濾的值。如圖一:
最常見的做法就是在TComboBox的Items屬性中硬碼寫入我們數據集中的字段名稱,然後在代碼中加入一大堆case或者if語句在判斷用戶選擇的字段來給數據集設置過濾器。
……
case ComboBox1.ItemIndex of
0:
ClIEntDataSet.Filtered := False;
ClIEntDataSet.Filter := ' F_CODE = ''' + Edit2.Text + '''';
ClIEntDataSet.Filtered := True;
1:
ClIEntDataSet.Filtered := False;
ClIEntDataSet.Filter := ' F_CHINESE_NAME = ''' + Edit2.Text + '''';
ClIEntDataSet.Filtered := True;
……
end;
或者用
….…
if ComboBox1.Text = '物料編碼' then
begin
ClIEntDataSet.Filtered := False;
ClIEntDataSet.Filter := ' F_CODE = ''' + Edit2.Text + '''';
ClIEntDataSet.Filtered := True;
end
else if ComboBox1.Text = '名稱' then
begin
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.Count - 1 do
if FIElds[i].Visible then
begin
FFields[iFieldsCount, 0] := Fields[i].FIEldName;
FFields[iFieldsCount, 1] := FIElds[i].DisplayLabel;
Inc(iFIEldsCount);
end;
ComboBox1.Items.Clear;
for i := 0 to iFIEldsCount - 1 do
ComboBox1.Items.Add(FFIElds[i, 1]);
end;
end;
這樣就實現了在運行時動態加載字段信息。這樣我的過濾器設置就變成了這樣的。
if ComboBox1.Text <> '' then
begin
ClIEntDataSet.Filtered := False;
ClientDataSet.Filter := FFIElds[ComboBox1.ItemIndex, 0] + '''' + Edit2.Text + '''';
ClIEntDataSet.Filtered := True;
end;
本方法無疑增加了代碼的靈活性,同時增加了代碼的復用度,因為代碼很好的隔離了變化的數據。因此只要在另一個也是要實現這種的功能的FORM中增加私有字段FFIElds: array[0..20, 0..2] of string 和使用上面的動態加載數據集字段過程,就可以說方便的實現了重用。但是這種重用並不是很好的,因為我們沒有實現很好的封裝性。導致在你的程序中到處散落有重復的代碼(你常常會通過COPY來獲得這個函數的重用,因為上面的代碼是沒有好的封裝性)。如果有一天你要修改數據裝載函數你就必須到處去找那裡拷貝了該函數——你也得修改散落在其他地方的代碼。於是我進行了再一次的重構,並對代碼進行了進一步的封裝。
代碼如下:
unit uDataSetFIEldsInfo;
// Description:單元包括 TDataSetFIEldsInfo 類,該類封裝了獲得數據集子段信息。
// 並提供了在combobox列表顯示字段顯示信息和獲得對應子段名稱的方法接口
// Created : wuchhao
// Date : 2003.5
interface
uses Classes, DBClIEnt, StdCtrls;
type
TDataSetFIEldsInfo = class
private
FFIEldsList: TStrings;
public
constructor Create;
destructor Destroy; override;
procedure GetDataSetFields(Source: TClIEntDataSet);
procedure ShowFIEldsInfo(Target: TComboBox);
function GetFIEldsNameByDisplayLabel(DisplayLabel: string): string;
end;
implementation
{ TDataSetFIEldsInfo }
constructor TDataSetFIEldsInfo.Create;
begin
FFIEldsList := TStringList.Create;
end;
destructor TDataSetFIEldsInfo.Destroy;
begin
FFIEldsList.Free;
inherited;
end;
procedure TDataSetFieldsInfo.GetDataSetFields(Source: TClIEntDataSet);
var
i: Integer;
begin
FFIEldsList.Clear;
with Source do
begin
for i := 0 to 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.GetFIEldsNameByDisplayLabel(
DisplayLabel: string): string;
var
index: Integer;
begin
Result := '';
index := FFIEldsList.IndexOf(DisplayLabel);
if index <> -1 then
Result := FFIEldsList.Strings[index+1] ;
end;
procedure TDataSetFieldsInfo.ShowFIEldsInfo(Target: TComboBox);
var
i: Integer;
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 <> '' then
begin
ClIEntDataSet.Filtered := False;
ClientDataSet.Filter := FFieldsInfo.GetFIEldsNameByDisplayLabel(ComboBox1.Text) + '''' + Edit2.Text + '''';
ClIEntDataSet.Filtered := True;
end;
通過調用FfIEldsInfo對象的接口過程來獲得對應的子段名稱。
本文是一個重構代碼的簡單例子,我想上面我實現的這個類還可以有很多種寫法和更好的算法。這裡只是提供一種關於重構代碼的思路,為提高我們的編寫代碼質量和它的可維護性、擴展性,探討OOD編程方式上的思路。