Delphi事件的广播2

原创 2006年05月13日 14:44:00

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

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

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

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

unit EventSubject;

interface
uses
  Classes;
type
  
//事件发布者的基类
  TEventSubject = class
  protected
    FObservers: IInterfaceList;
  public
    
//增加一个观察者
    procedure AddObserver(const Observer: IInterface);
    
//移除一个观察者
    procedure RemoveObserver(const Observer: IInterface);
    constructor Create;
    destructor Destroy; override;
  end;
implementation

{ TEventSubject }

procedure TEventSubject.AddObserver(const Observer: IInterface);
begin
  if FObservers.IndexOf(Observer) < 
0 then
    FObservers.Add(Observer);
end;

constructor TEventSubject.Create;
begin
  FObservers := TInterfaceList.Create;
end;

destructor TEventSubject.Destroy;
begin
  FObservers := nil;
  inherited;
end;

procedure TEventSubject.RemoveObserver(const Observer: IInterface);
begin
  FObservers.Remove(Observer);
end;

end.

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

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

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

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

... ...

{ TRectEventSubject }

procedure TRectEventSubject.BeforeRectChange(Rectangle: TRectangle);
var
  i: Integer;
begin
  for i := 
0 to FObservers.Count - 1 do
    if Supports(FObservers[i], IRectEvent) then
      (FObservers[i] as IRectEvent).BeforeRectChange(Rectangle);
end;

procedure TRectEventSubject.DoRectChange(Rectangle: TRectangle);
var
  i: Integer;
begin
  for i := 
0 to FObservers.Count - 1 do
    if Supports(FObservers[i], IRectEvent) then
      (FObservers[i] as IRectEvent).OnRectChange(Rectangle);
end;

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

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

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

end.

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

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

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

type
  TfmeDraw = class(TFrame, IRectEvent)
    ... ...
    
//IRectEvent
    procedure OnRectChange(Rectangle: TRectangle);
    procedure BeforeRectChange(Rectangle: TRectangle);
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

implementation

{$R *.dfm}

{ TfmeDraw }

... ...

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

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

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

destructor TfmeDraw.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!

相关文章推荐

《GOF设计模式》—创建型模式—Delphi源码示例:基于创建型模式的迷宫

示例:基于创建型模式的迷宫实现:考虑到MazeGame的成员函数CreatMaze所做的仅是创建一个有两个房间的迷宫,它是相当复杂的。显然有办法使它变得更简单。例如,Room的构造器可以提前用墙壁来初...

Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---观察者模式之WeatherReport

1.主题与观察者 {《HeadFirst设计模式》之观察者模式 } { 主题与观察者 } { 编译工具 :Delphi7.0 } { 联系方...
  • djCode
  • djCode
  • 2009年11月10日 14:47
  • 492

Delphi Android 广播事件处理

  • 2017年07月06日 20:34
  • 9.03MB
  • 下载

Delphi实现Android 广播事件监听(动态注册广播)

Android 下的广播简介在Android下,很多事件需要使用到系统下的广播。例如:你想知道手机电量的变化、是否安装了一个新的程序应用包、扩展介质被移除等信息时。Android广播分为两个方面:广播...
  • tanqth
  • tanqth
  • 2017年07月06日 21:00
  • 323

Rxjava2使用-构建事件总线(RxBus)代替原生广播

以前的项目中使用的是eventbus来实现事件的通知和订阅,rxjava2发布之后就使用了新的方式:Rxbus,减少添加的依赖库 源码:  ——— import android.support...

vue2中子组件修改父组件传入的prop,并向父组件$emit一个广播事件

1、在vue中:        prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态——这会让应用的数据流难以理解。   ...

Delphi中线程类TThread实现多线程编程2---事件、临界区、Synchronize、WaitFor……

现在开始说明 Synchronize和WaitFor   但是在介绍这两个函数之前,需要先介绍另外两个线程同步技术:事件和临界区 事件(Event)   事件(Event)与Delp...

Remoting事件(服务端广播改进).

  • 2008年07月04日 13:31
  • 114KB
  • 下载

广播事件处理

  • 2014年09月13日 18:04
  • 364KB
  • 下载

BLE协议栈Central 工程中与广播连接建立的事件

BLE协议栈Central 工程中与广播连接建立的事件         最近在改写BLE协议栈,需要实现实时发现周边从设备,并建立连接,获取RSSI的功能,没什么难度,但着实将各个事件好好看了一...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Delphi事件的广播2
举报原因:
原因补充:

(最多只允许输入30个字)