Delphi事件的广播2

上篇文章写了将事件分离成类的方法来实现事件的广播,这次将参考观察者模式来实现事件的广播。模式中主要有这两个角色:

发布者:发布者保存着一张观察者的列表,以便在必要的时候调用观察者的方法。

观察者:观察者是现实某些特定接口的类,对于发布者来说,它只关注这些接口,并不关注观察者具体是什么类。

为了让发布者更具通用性,我写了一个发布者的父类,它负责增删和管理观察者,一个类只要继续这个类,马上就有了发布者的特征,因此你也可以将这个单元作为你的发布者父类。看下面代码:

unitEventSubject;

interface
uses
Classes;
type
//事件发布者的基类
TEventSubject=class
protected
FObservers:IInterfaceList;
public
//增加一个观察者
procedureAddObserver(constObserver:IInterface);
//移除一个观察者
procedureRemoveObserver(constObserver:IInterface);
constructorCreate;
destructorDestroy;override;
end;
implementation

{TEventSubject}

procedureTEventSubject.AddObserver(constObserver:IInterface);
begin
ifFObservers.IndexOf(Observer)<
0then
FObservers.Add(Observer);
end;

constructorTEventSubject.Create;
begin
FObservers:=TInterfaceList.Create;
end;

destructorTEventSubject.Destroy;
begin
FObservers:=nil;
inherited;
end;

procedureTEventSubject.RemoveObserver(constObserver:IInterface);
begin
FObservers.Remove(Observer);
end;

end.

接下来是否将Rectangle类直接继续自EventSubject,我进行了一些思考,最后还是决定分离成一个独立的类,这样类的职责更加分明一些。不过之前得声明一个接口,这个接口提供了矩形事件的服务:

//矩形事件的接口
IRectEvent=Interface(IInterface)
[
'{C9FAFE6C-3C51-4B3F-9E73-E8EA898D4061}']
procedureOnRectChange(Rectangle:TRectangle);
procedureBeforeRectChange(Rectangle:TRectangle);
end;

而矩形事件发布者的实现相当的简单,只是遍历父类的FObservers列表,一一调用IRectEvent接口的方法:

//矩形事件发布者类
TRectEventSubject=class(TEventSubject)
public
procedureDoRectChange(Rectangle:TRectangle);
procedureBeforeRectChange(Rectangle:TRectangle);
end;

......

{TRectEventSubject}

procedureTRectEventSubject.BeforeRectChange(Rectangle:TRectangle);
var
i:Integer;
begin
fori:=
0toFObservers.Count-1do
ifSupports(FObservers[i],IRectEvent)then
(FObservers[i]asIRectEvent).BeforeRectChange(Rectangle);
end;

procedureTRectEventSubject.DoRectChange(Rectangle:TRectangle);
var
i:Integer;
begin
fori:=
0toFObservers.Count-1do
ifSupports(FObservers[i],IRectEvent)then
(FObservers[i]asIRectEvent).OnRectChange(Rectangle);
end;

上面有一点要注意的是,由于FObservers保存的是一张IInterface的列表,所以必须调用Supports方法判断该接口是否为IRectEvent,才能进行转接和调用。

矩形事件发布者类完成之后,即可将原来的事件广播类和事件触发类去掉,因为这两个类的职责已经由矩形事件发布者类代替了。然后在Rectangle类中声明一个TRectEventSubject成员并引出一个属性。最后我把Rectangle全局对象从MainFrm中移到自己的单元中,在初始化节中创建和在结束节中释放,毕竟我们认为矩形类是一开始就有的吗,这样更不依赖于外部的界面:

initialization
Rectangle:=TRectangle.Create;
finalization
Rectangle.Free;

end.

完成了wdRect单元的改造,接下来修改界面相关的单元。首先是主窗口,这里只剩下初始化矩形大小的代码了:

procedureTfrmMain.FormCreate(Sender:TObject);
begin
//初始化画布的属性
Rectangle.Width:=
100;
Rectangle.Height:=
100;
end;

而画布单元和矩形信息单元呢,这两个相当于观察者的具体类,所以它们要实现IRectEvent接口,并有要声明和实现接口的两个方法,这里只以DrawFrame为例,看下面代码:

type
TfmeDraw=class(TFrame,IRectEvent)
......
//IRectEvent
procedureOnRectChange(Rectangle:TRectangle);
procedureBeforeRectChange(Rectangle:TRectangle);
constructorCreate(AOwner:TComponent);override;
destructorDestroy;override;
end;

implementation

{$R*.dfm}

{TfmeDraw}

......

procedureTfmeDraw.OnRectChange(Rectangle:TRectangle);
begin
Rectangle.Draw(imgDraw.Canvas);
end;

procedureTfmeDraw.BeforeRectChange(Rectangle:TRectangle);
begin
Rectangle.Erase(imgDraw.Canvas);
end;

constructorTfmeDraw.Create(AOwner:TComponent);
begin
inherited;
Rectangle.RectEventSubject.AddObserver(IInterface(Self));
end;

destructorTfmeDraw.Destroy;
begin
Rectangle.RectEventSubject.RemoveObserver(IInterface(Self));
inherited;
end;

end.

我省略了很多不相关的代码,首先它是在构造方法中将自己加进RectEventSubject中,使之成为一个观察者;在构造方法中又将自己从RectEventSubject从移除,不知有人会不会有疑问:现实接口的对象的生命周期会由接口管理,那么fmeDraw的释放会不会由IRectEvent管理呢,答案是不会,具体原因请看我的另一篇文章:接口小论。上面代码中另外两个方法即是实现IRectEvent的方法,作用和上篇的事件是一样的。

TfmeInfo也遵循了相同的规则实现IRectEvent接口,不过有一点是必须注意的,现实接口的类必须实现接口中所有的方法,FmeInfo不能象上篇一样只得到OnRectChange的事件,它还要实现BeforeRectChange方法,不过既然没有用,把BeforeRectChange当成一个空方法就行了,也并不伤大雅。

至此,程序改造完毕,可以看到,改动其实并不大,不过与第一种方法相比,用Observer模式性能要低一些,拉动画布中的矩形,可以明显看到矩形的闪动。

用哪一种方法更好其实看具体应用,我个人更喜欢第一种方法,主要是性能要高一些,另外灵活性也并不输Observer模式。可见模式都要看具体的应用,也不能生搬硬套吧。而模式当然也不是死的,自己再多些思考,也许能找出比这两种更好的方法,期待你的发现,如果你有更好的方法,可以在留言中告知,如果你要完整的Demo,可以发邮件给我,我很乐意与你交流。

下一篇敬请期待,Bye!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值