zoukankan      html  css  js  c++  java
  • Delphi事件的广播2

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

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

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

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

    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,可以发邮件给我,我很乐意与你交流。

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

  • 相关阅读:
    十六进制内存赋值
    opcode修改
    C/C++ strtok函数
    pat1033. To Fill or Not to Fill (25)
    pat1008. Elevator (20)
    pat1089. Insert or Merge (25)
    pat1091. Acute Stroke (30)
    pat1002. A+B for Polynomials (25)
    pat1090. Highest Price in Supply Chain (25)
    pat1088. Rational Arithmetic (20)
  • 原文地址:https://www.cnblogs.com/findumars/p/5294091.html
Copyright © 2011-2022 走看看