用Delphi写MIS类程序(五)

 

1.7消除窗体的过度耦合

       在上一节中,我们整理出一个颇具雏形的框架(Framework),但这个框架还有一些不太理想的地方。

1.7.1消除ChildFormDefine中对子类的依赖

首先我们先来看ChildFormDefine.pas的内容,

……

implementation

uses

  MainForm;

……

  if Action = caFree then

    frmMain.RemoveMDIChild(self);

……

       在上面的代码中,我们可以看到在TCustomChildForm父类窗体中居然要引用子类(TfrmMain)的实现,这是有违面向对象的设计思想的,一旦新工程的MDI主窗体不是frmMain的话,那么我们这个框架就会出错了。那我们怎样去改进呢?

       Delphi中,我们可以通过Application对象中的MainForm属性就可以获取应用程序的主窗体,但返回的主窗体类型是TForm的,但我们可以肯定这个主窗体一定是TCustomMainForm的一个子类。所以我们可以把代码改写成下面的形式:

……

implementation

uses

  MainFormDefine;

……

  if Action = caFree then

    (Application.MainForm as TCustomMainForm).RemoveMDIChild(self);

……

这样,子窗体的父类就不再依赖于主窗体的子类了。

1.7.2消除MainForm中的子窗体引用

第二个不理想的地方在MainForm.pas中:

……

implementation

uses

  ChildFormC, ChildFormD;

……

       在这里我们可以看到,每当我们新增一个子窗体时,我们就必须修改MainForm中的内容,把这一新增的子窗体的单元名引入到上面的代码中,如果工程中有100个子窗体的话,MainForm中就会有100个子窗体的引用,看起来多恐怖啊!而且,当某一子窗体的文件要改名的话,那么MainForm中的内容也要接着修改;更麻烦的是,如果这些子窗体是由不同的开发人员各自开发的话,那么当这个工程要最后连编时,混乱的MainForm一定会带来不少的麻烦。那有什么好的方法去解决呢?

       我们来看一下,为什么MainForm中要引用这些这窗体,原因在于在MainForm中要调用CallChildForm这一方法,在这方法中是使用了某个子窗体的类型的作为参数的,这样一来,就必须引用该子窗体的单元了。也就是说,如果CallChildForm不使用子窗体的类型作参数的话,那么就可以解决这个问题了。因此我们打算把CallChildForm改成是这种型式:

CallChildForm(Sender, TfrmChildC); 改为 CallChildForm(Sender, 'TfrmChildC');

       也就是把第二个参数由子窗体类型改为字符串类型,通过这样的修改,我们就不在需要引用某一子类了,为了达到这个从类名转变为实际类的效果,我们需要一个类工厂。我们在../FlexMDIFramework中新增一个单元文件:FormClassMgrUnit.pas

{

MDI Tutorial v1.7

 Written by flexitime.

}

unit FormClassMgrUnit;

 

interface

uses

  Forms, Windows, Classes, SysUtils;

 

type

  TFormClass = class of TForm;

 

  TFormClassItem = class

    FormClass : TFormClass;

  end;

 

  TFormClassMgr = class

  private

    FList : TList;

  public

    constructor Create;

    destructor Destroy; override;

    function Items(index : integer) : TFormClassItem;

    function Count : integer;

    function GetItemByName(sFormClassName : string) : TFormClassItem;

    function CreateForm(sFormClassName: string): TForm;

    procedure Add(aFormClass : TFormClass);

  end;

 

var

  GFormClassMgr  : TFormClassMgr;

implementation

uses

  StrUtils;

 

{ TFormClassMgr }

 

procedure TFormClassMgr.Add(aFormClass: TFormClass);

var

  ai : TFormClassItem;

begin

  ai := TFormClassItem.Create;

  ai.FormClass := aFormClass;

  FList.Add(ai);

end;

 

function TFormClassMgr.Count: integer;

begin

  result := FList.Count;

end;

 

constructor TFormClassMgr.Create;

begin

  FList := TList.Create;

end;

 

function TFormClassMgr.CreateForm(sFormClassName: string): TForm;

var

  ai : TFormClassItem;

begin

  result := nil;

  ai := GetItemByName(sFormClassName);

  if ai = nil then exit;

  result := TForm(ai.FormClass.NewInstance);

  result.Create(Application.MainForm);

end;

 

destructor TFormClassMgr.Destroy;

var

  i : integer;

begin

  for i := 0 to Count - 1 do

    begin

      Items(i).Free;

    end;

  FList.Free;

  inherited;

end;

 

function TFormClassMgr.GetItemByName(sFormClassName : string): TFormClassItem;

var

  i : integer;

  s1 : string;

begin

  result := nil;

  s1 := UpperCase(sFormClassName);

 

  for i := 0 to Count - 1 do

    begin

      if (s1 = UpperCase(Items(i).FormClass.ClassName)) then

        begin

          result := Items(i);

          exit;

        end;

    end;

end;

 

function TFormClassMgr.Items(index: integer): TFormClassItem;

begin

  result := FList.Items[index];

end;

end.

       在这个单元文件中,我们引入了一个全局的变量GFormClassMgr  : TFormClassMgr; 这个变量引用的是一个子窗体的管理器,在使用这个管理器时,通过Add(aFormClass: TFormClass); 来把所有的子窗体类型都登记在这个管理器中,然后当需要创建某一个子窗体时,只要调用 CreateForm(sFormClassName: string): TForm; 这个方法就可以得到某一子窗体的实例了。代码比较简单,就不再详细说明了。

       增加了上面的单元文件后,我们接着就要修改MainFormDefine.pas中的内容,增加一个新的CallChildForm方法,并且保留原来的方法,所以这里要使用重载。

……

function CallChildForm(Sender: TObject; AClass: TCustomChildFormClass): TCustomChildForm; overload;

function CallChildForm(Sender: TObject; AClassName: string): TCustomChildForm; overload;

……

function TCustomMainForm.CallChildForm(Sender: TObject;

  AClassName: string): TCustomChildForm;

var

  af : TForm;

begin

  result := SearchMDIChild(Sender);

  if result = nil then

    begin

      af := GFormClassMgr.CreateForm(AClassName);

      if af is TCustomChildForm then

        begin

          result := af as TCustomChildForm;

          AddMDIChildList(Sender, result);

        end

      else

        result := nil;

    end;

  if result <> nil then

    result.Show;

end;

       在新的CallChildForm中,我们调用了GFormClassMgr.CreateForm(AClassName);这个方法来产生新的子类。

最后我们要修改的是MDI_Tutorial.dpr文件,

……

begin 

  GFormClassMgr := TFormClassMgr.Create;

  GFormClassMgr.Add(TfrmChildC);

  GFormClassMgr.Add(TfrmChildD);

  Application.Initialize;

  Application.CreateForm(TfrmMain, frmMain);

  Application.Run;

  GFormClassMgr.Free;

End.

       我们在dpr文件中为GFormClassMgr进行初始化,并把所有的子类窗体都增加或者说是注册到GFormClassMgr中,只有进行这一步,才可以使得刚才的GFormClassMgr.CreateForm 代码能正常地工作。

       把所有上面的工作完成后,我们就可以为MainForm.pas进行解耦了

……

var

  frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure TfrmMain.mnTestC1Click(Sender: TObject);

begin

  inherited;

  CallChildForm(Sender, 'TfrmChildC');

end;

procedure TfrmMain.mnTestD1Click(Sender: TObject);

begin

  inherited;

   CallChildForm(Sender, 'TfrmChildD');

end;

       在这里,我们不再需要引用任何的子窗体文件了。

1.7.3进一步降低耦合度

       我们完成了上一小节的工作后,整个框架的耦合度已经有了很大的改善。但我们回头来看一下最后修改的MDI_Tutorial.dpr文件,在这个文件里,我们把所有子类的增加(注册)都放到这里,这样就等于增加了这个文件与子窗体文件的耦合度了,虽然说这个MDI_Tutorial.dpr由于Delphi编译机制的原因,本来就必须引用这些子窗体文件的,所以这样的改变本来算不上什么,但如果我们每增加一个子窗体都必须修改这个dpr文件的话,这样也是非常不好的。所以我们把在dpr完成的注册过程交回给每个子窗体,让它们自己来完成注册这一步,这样就降低了耦合度了。那怎么去修改呢?我们来看ChildFormC.pas中的代码:

……

implementation

uses

  FormClassMgrUnit;

……

initialization

  GFormClassMgr.Add(TfrmChildC);

end.

       我们在这个文件中,加入了FormClasMgrUnit的引用,然后在单元的结尾增加了一个初始化段(initialization),并在这个初始化段里面实现了类的注册。这里要说明一下initialization段,Delphi的编译器会在这个单元文件被第一次加载时就执行initialization段中的代码,因此我们把类的注册放到这里来了。

       经过这样的修改,我们就不再需要dpr里面那些GFormClassMgr.Add()的代码了,但现在的程序能运行吗?答案是不行的,为什么呢?原因是当ChildFormC.pas中执行initialization中的内容时,GFormClassMgr还没有初始化!因此我们要接着修改FormClassMgrUnit.pas中的内容,在这个文件里面,我们也增加一个initialization段,

……

initialization

  GFormClassMgr := TFormClassMgr.Create;

…..

       最后要修改的是MDI_Tutorial.dpr文件中的uses顺序,我们要保证GFormClassMgr要在所有的子窗体注册前完成初始化。所以调整一下uses的顺序就行了。

      

 

 

       在第一章中,我们通过一步一步的学习和提炼,完成了一个MDI程序的基本框架。通过这个框架,我们可以很简单的完成子窗体的创建、显示和关闭功能,同时也建立一个良好的读取、保存、退出提示的机制。在最后一节中,我们引用了类工厂的方式来为我们的框架解耦,这一工作从目前来看,好像并不能为我们带来直接的收益。但只有经过这样的解耦,我们才可能在后续章节中讨论如何动态加载DLLBPL中的窗体。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值