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

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

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

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

unit EventSubject;

interface
uses
  Classes;
type
   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接口的方法:

to FObservers.Count -  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 :=  to FObservers.Count -  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.Height := ;
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,可以发邮件给我,我很乐意与你交流。

http://blog.csdn.net/linzhengqun/article/details/727271

Delphi事件的广播2的更多相关文章

  1. Delphi事件的广播 转

    http://blog.sina.com.cn/s/blog_44fa172f0102wgs2.html 原文地址:Delphi事件的广播 转作者:MondaySoftware 明天就是五一节了,辛苦 ...

  2. Delphi事件的广播

    原文地址:Delphi事件的广播 转作者:MondaySoftware 明天就是五一节了,辛苦了好几个月,借此机会应该尽情放松一番.可是想到Blog好久没有写文章,似乎缺些什么似的.这几个月来在项目中 ...

  3. Delphi事件的广播 good

    明天就是五一节了,辛苦了好几个月,借此机会应该尽情放松一番.可是想到Blog好久没有写文章,似乎缺些什么似的.这几个月来在项目中又增长了许多经验,学到许多实际应用的知识.不如把一些比较有用的记录下来, ...

  4. [转载]Delphi事件的广播

    https://blog.csdn.net/dropme/article/details/975736 明天就是五一节了,辛苦了好几个月,借此机会应该尽情放松一番.可是想到Blog好久没有写文章,似乎 ...

  5. delphi 事件和属性的绑定

    TWindowState = (wsNormal, wsMinimized, wsMaximized); TScrollingWinControl = class(TWinControl) priva ...

  6. Delphi事件列表赏析(38个事件,必须要对这些事件非常熟悉,才能如臂使指,才能正确发布到新控件!)

    我把Delphi常用的几个类的事件都收集齐了,并一一加以注释.原因是在自定义的过程中,看到那堆长长的事件列表感到头晕,但是如果不发布这些事件的话,更是暴殄天物.所以关键还是要对这些事件非常熟悉,才能不 ...

  7. QT信号槽与Delphi事件的对比

    最近学QT,对信号槽机制感到有点新鲜: QObject::connect(slider, SIGNAL(valueChanged(int)), lcd, SLOT(display(int))); 自己 ...

  8. Delphi消息的广播方式(先RegisterWindowMessage,后SendMessage HWND_BROADCAST,最后改写接收窗口的WndProc)

    ///////消息广播只能将消息传递到接收消息的主程序中,MDIChild窗体不能接收到广播消息:///////// unit Unit1; interface uses Windows, Messa ...

  9. VB.net Wcf事件广播(订阅、发布)

    这篇东西原写在csdn.net上,最近新开通了博客想把零散在各处的都转移到一处.   一.源起 学WCF有一段时间了,可是无论是微软的WebCast还是其他网上的教程,亦或我购买的几本书中,都没有怎么 ...

随机推荐

  1. spring MVC 如何获取session并实现传值到前台

    后台获取session: @RequestMapping("/usrlogin") public ModelAndView usrlogin(@RequestParam Strin ...

  2. solr 从零学习开始

    2010-10 目 录 1 1.1 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4. ...

  3. 浅谈MySql的存储引擎(表类型) (转)

    什么是MySql数据库 通常意义上,数据库也就是数据的集合,具体到计算机上数据库可以是存储器上一些文件的集合或者一些内存数据的集合. 我们通常说的MySql数据库,sql server数据库等等其实是 ...

  4. RVCT的Linux环境变量配置 ARM® RVDS™ 4.1(b713)

    下载解压 armrvds.tar.gz到/opt 下 在自己的build.sh下导入RVCT的环境变量配置ARM® RVDS™4.1(b713): export ARMROOT=/opt/armrvd ...

  5. 初探 插头DP

    因为这题,气得我火冒三丈! 这数据是不是有问题啊!我用cin代替scanf后居然就AC了(本来一直卡在Test 18)!导致我调(对)试(排)了一个小时!! UPD:后来细细想想,会不会是因为scan ...

  6. 查询SystemFeature的方法

    查询SystemFeature的方法可以在adb shell下敲如下的命令: dumpsys package 然后搜feature关键字. 例如,我的平台的SystemFeature,如下所示: Fe ...

  7. PHP - 接口 - 多接口

    /* * 使用多接口 */ //定义接口1 interface IPerosn_one{ public function eat(); } //定义接口2 interface IPerson_two{ ...

  8. net core 中间件详解及项目实战

    net core 中间件详解及项目实战 前言 在上篇文章主要介绍了DotNetCore项目状况,本篇文章是我们在开发自己的项目中实际使用的,比较贴合实际应用,算是对中间件的一个深入使用了,不是简单的H ...

  9. perl 使用SUPER类来访问覆盖的方法

    有时候,你希望一个衍生类的方法表现得象基类中的某些方法的封装器 这就是 SUPER 伪类提供便利的地方.它令你能够调用一个覆盖了的基类方法,而不用声明 是哪个类定义了该方 法.(注:不要把这个和第十一 ...

  10. jquery子元素过滤选择器

    :nth-child('索引值')//获取指定元素下的某个子元素的位置,索引从1开始: //偶数行                 //$('li:nth-child(even)').addClass ...