抽象提高可维护性,实例来证明(delphi)
——面向对象编程方法的一个例子
Allen Tao(http://allentao430.spaces.live.com)
2007-06-29      软件开发中面临的最大问题是维护,很多精力花在应对客户不断变化的需求而做的修改上。为了提高程序的可维护性,面向对象编程方法中有两个原则,一个是依赖于抽象而不依赖于具体,另一个是用扩展来代替修改。为了很好地说明这两个原则,我做了以下的例子。
     功能说明:程序的结构是一个界面类调用另一个类的方法,并将方法的结果显示在文本框中。
     界面元素:界面上放置一个按钮,Name为btn1;一个文本框,Name为txt1。如下所示: 传统做法
     先写被创建的功能类的代码。创建一个单元文件Unit2.。其中的代码为:
unit Unit2; interface
type TA=class
public
function GetResult:String;
end;implementation function TA.GetResult: String;
begin
Result:= 'PLAIN';
end; end.      上面的代码是建立了一个类TA,它有一个公共方法GetResult,其中完成了某种运算,得出了一个结果字符串。接着再在界面类的按扭点击事件处理方法中使用该类。代码如下:
unit Unit1; interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Unit2; type TForm1 = class(TForm)
btn1: TButton;
txt1: TEdit;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end; var
Form1: TForm1; implementation {$R *.dfm} procedure TForm1.btn1Click(Sender: TObject);
begin
txt1.Text:= TA.Create.GetResult;
end; end.      以上的代码就完成了功能的要求,最终运行结果如下:
     这是传统的方法,也是最快地实现功能的方法。但这种方法在客户的需求发生变化时就暴露出很大的不足。比如说客户对TA.GetResult方法的计算公式不满意,要求用一种新的公式。在这种方法下,就需要将TA.GetResult方法中的代码进行替换,但其实这样做是不好的。因为原有的代码已经很好地满足了原来的需求,并不是本身存在错误而需要改正,将正确的代码抛弃是一种浪费。而且,客户今天要你改为这种方式,明天还可能要求你改回原来的方式。这样的话,就只有哭了。比较好的方法是把新的计算公式放到另一个类TB中,而保留原有的TA代码。这就是“用扩展代替修改”的原则。为了实现这个原则,还需要对原有的代码做一些修改,也就是做一下抽象。 
为扩展而抽象的做法
     因为需求的变化只是针对GetResult方法的内部,而它的输入输出没有变化,因此可以对该方法进行抽象,形成一个抽象类。该抽象类的代码为:
TBase=class
public
function GetResult:string; virtual; abstract;
end;
     再使TA继承该类,这样TA的声明变为:
TA=class(TBase)
public
function GetResult:String; override;
end;
     这样就完成了对TA类的抽象。但是这并没有完全遵守“依赖于抽象而不依赖于具体”原则,因为此时界面类中还是用的TA类的实例,应改为使用TBase类的实例,这样界面类就是依赖于TBase抽象类而不依赖于TA具体类了。为此,需要先在Unit2中写一个全局工厂方法,用于创建TBase类的实例。
     声明部分:
function GetBaseInstance: TBase;
     实现部分:
function GetBaseInstance: TBase;
begin
Result:= TA.Create;
end;
     接着在界面类按钮点击事件处理方法中改为以下代码:
txt1.Text:= GetBaseInstance.GetResult;
     至此就完成了为扩展而做的封装准备。再针对用户的需求做出扩展,在Unit2中建立同样继承于TBase的类TB,代码如下:
     声明部分:
TB=class(TBase)
public
function GetResult:String; override;
end;      实现部分:
function TB.GetResult:string;
begin
Result:= 'PLAINB';
end;
     该类的GetResult方法已根据客户要求做了改变。为了让界面类使用这个新类的方法,只需在工厂方法GetBaseInstance中改为实例化TB的对象即可,即:
Result:= TB.Create;
     这样,界面类中的调用代码不需做任何改变,即可实现功能的变化。运行后,结果显示为:      最后,如果客户要求改为原来的功能,只需简单地在GetBaseInstance方法中将TB改回TA即可。      通过上面的例子,可以得出这样的结论:为了应对客户需求的变化与反复,不应对已成熟的代码进行修改,而应在成熟代码之外进行扩展;为了扩展的方便需要将代码进行抽象,并让调用者使用抽象对象;对抽象的依赖使得在扩展时避免不需变化的部分(比如调用代码)发生不必要的变化。由此可见抽象对提高代码的可维护性能起到十分积极的作用。      抽象的作用不仅表现在提高代码的可维护性上,它还可以提高代码的可扩展性。对于这个问题,我也做了一个例子,如有兴趣请访问我的博客:http://allentao430.spaces.live.com,文章标题为《抽象提高可扩展性,实例来证明(delphi)——面向对象编程方法的又一个例子》。      上面例子的最终代码为:
unit Unit1; interface uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Unit2; type TForm1 = class(TForm)
btn1: TButton;
txt1: TEdit;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end; var
Form1: TForm1; implementation {$R *.dfm} procedure TForm1.btn1Click(Sender: TObject);
begin
txt1.Text:= GetBaseInstance.GetResult;
end; end. ----------------------------------------------------------------------------------
unit Unit2; interface type TBase=class
public
function GetResult:string; virtual; abstract;
end; TB=class(TBase)
public
function GetResult:String; override;
end; TA=class(TBase)
public
function GetResult:String; override;
end; function GetBaseInstance: TBase; implementation function GetBaseInstance: TBase;
begin
Result:= TC.Create;
end; function TA.GetResult:string;
begin
Result:= 'PLAIN';
end; function TB.GetResult:string;
begin
Result:= 'PLAINB';
end; end.