解耦:Delphi下IoC 模式的实现
摘要:在Delphi下IoC模式的多种实现
Ioc英文为 Inversion of Control,即反转模式,这里有著名的好莱坞理论:你呆着别动,到时我会找你。Ioc模式是解决调用者和被调用者之间关系的模式,可以有效降低软件的耦合度,并适合团队开发,使用这种模式需要首先设计一个好的框架,也可以称之为IoC容器
(可能这样的说法在Java世界更Cool J)。其实Windows内部就存在这样的模式,称之为
Callback(回调),在Delphi 的源代码中也有很多这样的方式。看一个例子:
可以打开下面两个文件
$(Delphi)\Source\Vcl\DB.pas
$(Delphi)\Source\Vcl\DBLogDlg.pas
第二个文件是一个需要用户填写登录用户名和密码的对话框,显然这属于用户交互层,
我们不希望在DB.pas(数据逻辑层)中直接调用这个功能,Delphi是这样实现的:
DB.pas #2282 有这样的声明:
LoginDialogProc: function (const ADatabaseName: string; var AUserName, APassword: string): Boolean;
LoginDialogProc 被定义为函数类型变量。
在DBLogDlg.pas #132
initialization
if not Assigned(LoginDialogProc) then
LoginDialogProc := LoginDialog;
也就是在DBLogDlg.pas单元初始化阶段给DB.Pas中的LoginDialogProc赋予实现函数。
我觉得这个例子已经足够把问题说得清楚了。
当然这并非实现IoC的唯一方法,对于不同的语言都有自己的特性,上面的方法可能比较古老的一种,通过函数指针来实现,其他的方法还有
一:封装为接口
我们看到在DB.pas 中声明了很多类似的函数变量,我们可以将其封装为一个接口
IDBDlogInterface = Interface
['{06B5DEAC-0BB1-4658-91F6-E78004DF131D}']
LoginDialogProc: function (const ADatabaseName: string; var AUserName, APassword: string): Boolean;
LoginDialogExProc: function (const ADatabaseName: string; var AUserName, APassword: string; NameReadOnly: Boolean): Boolean;
RemoteLoginDialogProc: function (var AUserName, APassword: string): Boolean;
end;
然后申明一个全局的接口类型变量
Var
DBDialog : IDBDialogInterface ;
然后在其他单元实现这个接口,并初始化时赋值到这个接口变量。
二:注册机制的IoC
这个例子稍微复杂一些,但是似乎更实用
我们在一个窗口上放置了一个pageControl,我们知道一个pageControl上可以放置多个pageControl,而且这多个 pageControl功能相对独立,没有耦合。我们可以这样做:建立这个主窗体放置一个空的PageControl,然后声明一个 TInterfaceList变量用来存储PageControl的具体实现。每一个PageControl动态创建,使用注册的Frame来填充
代码如下:
unit frmStartPage;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ImgList,Contnrs, ComCtrls, ExtCtrls;
Type
//填充pageControl的Frame基类
TPageFrame = class(TFrame)
protected
function Caption :String ; virtual; abstract;
end;
TPageFrameClass = class of TPageFrame ;
TStartPage = class(TForm)
PageControl1: TPageControl;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
StartPage :TStartPage ;
RegTabSheetClasses: TClassList;
procedure RegisterStartPageTabSheet( AFrameClass :TPageFrameClass);
implementation
{$R *.dfm}
procedure TStartPage.FormCreate(Sender: TObject);
var
i:integer;
lTabSheet : TTabSheet;
lFrame : TPageFrame;
begin
for i:= 0 to RegTabSheetClasses.Count -1 do
begin
lTabSheet := TTabSheet.Create(PageControl1);
lFrame := TPageFrameClass( RegTabSheetClasses[i]).Create(self) as TPageFrame;
lTabSheet.Caption := lFrame.Caption ;
lFrame.Align := alClient;
lTabSheet.InsertControl(lFrame);
lTabSheet.PageControl := PageControl1;
lTabSheet.PageIndex := 0 ;
end;
end;
procedure RegisterStartPageTabSheet( AFrameClass :TPageFrameClass);
begin
RegTabSheetClasses.Add(AFrameClass);
end;
initialization
RegTabSheetClasses := TClassList.Create ;
finalization
RegTabSheetClasses.Free;
end.
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,frmStartPage, StdCtrls;
type
TFrame2 = class(TPageFrame)
Label1: TLabel;
private
{ Private declarations }
protected
{ Public declarations }
function Caption :String ; override;
public
end;
implementation
{$R *.dfm}
{ TFrame2 }
function TFrame2.Caption: String;
begin
result := 'Frame1';
end;
initialization
RegisterStartPageTabSheet(TFrame2);
end.