本文来自:http://www.cnblogs.com/hezihang/p/6083555.html

Delphi采用接口方式设计模块,可以降低模块之间的耦合,便于扩展和维护。本文提供一个实现基于接口(IInterface)方式的监听器模式(观察者模式、订阅者模式),实现一个自动多播器。

下面程序在Berlin下测试通过,其他Delphi版本未测试,未进行跨平台测试(应该可以支持)

1.prepare

在观察者模式中采用接口,可以将相关函数汇合为接口。

举例:假设我们窗口有一个TTreeView,用于显示应用中的对象,用户通过点击TreeView中的不同对象,切换其他多个不同窗口中的显示内容。

在传统的方式下,维护一个通知列表,可以采用TreeView.OnChange事件中调用所有通知,然后处理如下:(也可采用多播方式,详见:http://www.cnblogs.com/hezihang/p/3299481.html)

procedure TForm2.TreeView1Change(Sender: TObject; Node: TTreeNode);
var
L:TTVChangedEvent;
begin
for L in FList do //FList:TList<TTVChangedEvent>
L(Sender, Node);
end;

显然采用传统方式,各窗口都需要uses TreeView所在窗口。

另外,如果TreeView所在窗口还有其他事件需要对多个外部窗口或对象进行通知,

则再需要建立一个通知列表。

采用事件方式,需要自己维护一个或多个通知列表,同时各个使用事件的单元都需要引用事件源的单元。

2.

如果我们采用接口方式,将所有事件包含进去,由TreeView所在窗口去调用:

type
{$M+}
ICurrentStateObserver=interface
['{20E8D6CB-3BCF-4DAE-A6CE-FEA727133C57}']
procedure OnCurrentObjectChange(CurObj:Pointer);
procedure OnDataReceive(Buf:Pointer; Size:Integre);
procedure OnResize(W, H:Integer);
end;
{$M-}

注意实现自动观察者的接口必须打开RTTI,{$M+}

然后,只需要如下调用,就可以让所有监听者(观察者)的OnResize被调用(接口内所有方法均可被调用):

procedure TForm2.FormResize(Sender: TObject);
begin
CurrentStateDispatcher.Source.OnResize(Width, Height);
end;

其中:

(1).

CurrentStateDispatcher.Source:ICurrentStateObserver
是一个虚拟接口,也是ICurrentStateObserver类型。调用此接口内的方法,就自动调用所有观察者所对应方法。
这样我们只需要调用
CurrentStateDispatcher.Source.OnCurrentObjectChange(...);
CurrentStateDispatcher.Source.OnDataReceive(...);
CurrentStateDispatcher.Source.OnResize(...);
就可以实现所有观察者的调用。

(2).
CurrentStateDispatcher:IInterfaceObservable<ICurrentStateObserver>
IInterfaceObservable<ICurrentStateObserver>是一个实现ICurrentStateObserver的多播监听器模式(观察者模式)的接口:
  IInterfaceObservable < T: IInterface >= interface
procedure AddObserver(const aListener: T);
procedure RemoveObserver(const aListener: T);
function GetSource: T;
property Source: T read GetSource;
end;

AddObserver是添加监听者,RemoveObject是删除观察者

Source就是前面提到的用于多播调用的虚拟接口。

(3).在使用模式的对象中声明:
  TForm2=class(TForm)
....
private
FCurrentStateDispatcher:IInterfaceObservable<ICurrentStateObserver>;
public
property CurrentStateDispatcher:IInterfaceObservable<ICurrentStateObserver> read FCurrentStateDispatcher;
end;
3.
下面我们看一个完整的使用例子:
uMainForm.pas
unit uMainForm;

interface

uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs , uInterfaceObservable, Vcl.StdCtrls,
Vcl.ComCtrls; type
{$M+}
ITestObserver=interface
['{FE7F7C11-13BC-472A-BB7A-6536E20BCEDD}']
procedure OnClick(Sender:TObject);
procedure OnResize(Sender:TObject; X, Y:Integer);
end;
{$M-} TForm2=class;
TObserver1=class(TInterfacedObject, ITestObserver)
F:TForm2;
procedure OnClick(Sender:TObject);
procedure OnResize(Sender:TObject; W, H:Integer);
constructor Create(Owner:TForm2);
end; TObserver2=class(TInterfacedObject, ITestObserver)
F:TForm2;
procedure OnClick(Sender:TObject);
procedure OnResize(Sender:TObject; W, H:Integer);
constructor Create(Owner:TForm2);
end; TForm2 = class(TForm)
Memo2: TMemo;
procedure FormClick(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FTestDispatcher:IInterfaceObservable<ITestObserver>;
public
{ Public declarations }
property TestDispatcher:IInterfaceObservable<ITestObserver> read FTestDispatcher;
end; var
Form2: TForm2; implementation {$R *.dfm} procedure TForm2.FormClick(Sender: TObject);
begin
FTestDispatcher.Source.OnClick(Sender);
end; procedure TForm2.FormCreate(Sender: TObject);
begin
FTestDispatcher:=TDioInterfaceDispatcher<ITestObserver>.Create;
FTestDispatcher.AddObserver(TObserver1.Create(Self));
FTestDispatcher.AddObserver(TObserver2.Create(Self));
end; procedure TForm2.FormDestroy(Sender: TObject);
begin
FTestDispatcher:=nil;
end; procedure TForm2.FormResize(Sender: TObject);
var
// i:Integer;
// T:LongWord;
W, H:Integer;
begin
W:=Width;
H:=Height;
// T:=GetTickCount;
// for i := 0 to 1000000 do
TestDispatcher.Source.OnResize(Sender, W, H);
// ShowMessage(IntToStr(GetTickCount- T));
end; { TObserver1 } constructor TObserver1.Create(Owner: TForm2);
begin
F:=Owner;
end; procedure TObserver1.OnClick(Sender: TObject);
begin
F.Memo2.Lines.Add('TObserver1.OnClick');
end; procedure TObserver1.OnResize(Sender: TObject; W, H:Integer);
begin
F.Memo2.Lines.Add(Format('TObserver1.OnResize:%d, %d', [W, H]));
end; { TObserver2 } constructor TObserver2.Create(Owner: TForm2);
begin
F:=Owner;
end; procedure TObserver2.OnClick(Sender: TObject);
begin
F.Memo2.Lines.Add('TObserver2.OnClick');
end; procedure TObserver2.OnResize(Sender: TObject; W, H:Integer);
begin
F.Memo2.Lines.Add(Format('TObserver2.OnResize:%d, %d', [W, H]));
end; end.

uMainForm.dfm

object Form2: TForm2
Left =
Top =
Caption = 'Form2'
ClientHeight =
ClientWidth =
OnClick = FormClick
OnCreate = FormCreate
OnDestroy = FormDestroy
OnResize = FormResize
TextHeight =
object Memo2: TMemo
Left =
Top =
Width =
Height =
Align = alBottom
TabOrder =
end
end

4.

下面是uInterfaceObservable.pas

unit uInterfaceObservable;

interface

uses System.Generics.Collections, System.TypInfo, System.Rtti;

type
IInterfaceObservable < T: IInterface >= interface
procedure AddObserver(const aListener: T);
procedure RemoveObserver(const aListener: T);
function GetSource: T;
property Source: T read GetSource;
end; TDioInterfaceDispatcher<T: IInterface> = class(TInterfacedObject, IInterfaceObservable<T>)
protected
class var FTypeInfo: PTypeInfo;
class var FMethods: TArray<TRttiMethod>;
class var FIID: TGUID;
class constructor Create;
protected
FList: TList<T>;
FVirtualSource, FSource: T;
FVirtualInterface: TVirtualInterface;
FEvents: TObjectList<TList<TMethod>>;
procedure MethodInvoke(Method: TRttiMethod; const Args: TArray<TValue>;
out Result: TValue);
public
procedure AddObserver(const aListener: T);
procedure RemoveObserver(const aListener: T);
function GetSource: T;
constructor Create;
destructor Destroy; override;
property Source: T read FSource;
end; implementation uses System.SysUtils; { TDioDispatcher<T> } procedure TDioInterfaceDispatcher<T>.AddObserver(const aListener: T);
type
TVtable = array [ .. ] of Pointer;
PVtable = ^TVtable;
PPVtable = ^PVtable;
var
i: Integer;
M: TMethod;
P: Pointer;
begin
FList.Add(aListener);
P:=IInterface(aListener);
// P := IInterfaceGetObject(aListener).GetObject;
for i := to FEvents.Count - do
begin
// 3 is offset of Invoke, after QI, AddRef, Release
M.Code := PPVtable(P)^^[ + i ] ;
M.Data := P;
FEvents[i].Add(M);
end;
if FList.Count= then
FSource:=aListener
else
FSource:=FVirtualSource;
end; procedure TDioInterfaceDispatcher<T>.MethodInvoke(Method: TRttiMethod;
const Args: TArray<TValue>; out Result: TValue);
var
L:TList<TMethod>;
M:TMethod;
i:Integer;
begin
L:=FEvents[Method.VirtualIndex-];
i:=;
while i<L.Count do
begin
M:=L[i];
Args[]:=M.Data;
System.Rtti.Invoke(M.Code, Args, Method.CallingConvention, nil);
if (M=L[i]) then
Inc(i);
end;
end; constructor TDioInterfaceDispatcher<T>.Create;
var
i: Integer;
LMethod: TRttiMethod;
E: TList<TMethod>;
S:String;
begin
inherited Create;
FEvents := TObjectList<TList<TMethod>>.Create(True);
FList := TList<T>.Create;
FVirtualInterface := TVirtualInterface.Create(FTypeInfo);
FVirtualInterface.OnInvoke := Self.MethodInvoke;
FVirtualInterface.QueryInterface(FIID, FVirtualSource);
Assert(Assigned(FVirtualSource), '未找到接口' + GUIDToString(FIID));
FSource:=FVirtualSource;
for i := to High(FMethods) do
begin
E := TList<TMethod>.Create;//TEvent.Create(LMethod, FTypeInfo, i);
FEvents.Add(E);
end;
end; class constructor TDioInterfaceDispatcher<T>.Create;
var
LType: TRttiType;
FContext: TRttiContext;
begin
FTypeInfo := TypeInfo(T);
LType := FContext.GetType(FTypeInfo);
FIID := TRttiInterfaceType(LType).GUID;
FMethods := LType.GetMethods();
//Assert(Length(FMethods) <= , '只能分发30个以内函数的接口!');
end; destructor TDioInterfaceDispatcher<T>.Destroy;
var
i: Integer;
begin
FSource := nil;
FVirtualSource:=nil;
FVirtualInterface := nil;
FList.DisposeOf;
FEvents.DisposeOf;
inherited;
end; function TDioInterfaceDispatcher<T>.GetSource: T;
begin
Result := FSource;
end; procedure TDioInterfaceDispatcher<T>.RemoveObserver(const aListener: T);
var
N, i: Integer;
begin
N := FList.IndexOf(aListener);
if N >= then
begin
for i := to FEvents.Count - do
FEvents[i].Delete(N);
end;
FList.Remove(aListener)
end; end.

Delphi的基于接口(IInterface)的多播监听器模式(观察者模式 )的更多相关文章

  1. Eclipse 基于接口编程的时候,快速跳转到实现类的方法(图文)

    Eclipse 基于接口编程的时候,要跳转到实现类很麻烦,其实Eclipse已经实现该功能. 只要按照Ctrl键,把鼠标的光标放在要跳转的方法上面,第一个是跳转到接口里面,第二个方法是跳转到实现类的位 ...

  2. mybatis整合spring 之 基于接口映射的多对一关系

    转载自:http://my.oschina.net/huangcongmin12/blog/83731 mybatis整合spring 之  基于接口映射的多对一关系. 项目用到俩个表,即studen ...

  3. 【java爬虫】---爬虫+基于接口的网络爬虫

    爬虫+基于接口的网络爬虫 上一篇讲了[java爬虫]---爬虫+jsoup轻松爬博客,该方式有个很大的局限性,就是你通过jsoup爬虫只适合爬静态网页,所以只能爬当前页面的所有新闻.如果需要爬一个网站 ...

  4. 基于接口回调详解JUC中Callable和FutureTask实现原理

    Callable接口和FutureTask实现类,是JUC(Java Util Concurrent)包中很重要的两个技术实现,它们使获取多线程运行结果成为可能.它们底层的实现,就是基于接口回调技术. ...

  5. was集群下基于接口分布式架构和开发经验谈

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/luozhonghua2014/article/details/34084935    某b项目是我首 ...

  6. Net系列框架-Dapper+AutoFac 基于接口

    Net系列框架-Dapper+AutoFac 基于接口 工作将近6年多了,工作中也陆陆续续学习和搭建了不少的框架,后续将按由浅入深的方式,整理出一些框架源码,所有框架源码本人都亲自调试通过,如果有问题 ...

  7. 最简单的动态代理实例(spring基于接口代理的AOP原理)

    JDK的动态代理是基于接口的 package com.open.aop; public interface BusinessInterface {     public void processBus ...

  8. Spring AOP 介绍与基于接口的实现

    热烈推荐:超多IT资源,尽在798资源网 声明:转载文章,为防止丢失所以做此备份. 本文来自公众号:程序之心 原文地址:https://mp.weixin.qq.com/s/vo94gVyTss0LY ...

  9. 配置基于接口地址池的DHCP

    配置基于接口地址池的DHCP 原理概述 DHCP(动态主机配置协议),采用C/S方式工作,C向S动态请求配置信息,S自动分配配置信息. 基于接口地址池的DHCP服务器,链接这个接口网段的用户都可以从该 ...

随机推荐

  1. Iterator 迭代器(一)

         迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址.迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为 ...

  2. dp和px以及sp

    dp(dip): device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA.HVGA和QVGA 推荐使用这个,不依赖 ...

  3. spring boot 1.4.1 with jsp file sample

    <!--pom.xml--> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=" ...

  4. js 毫秒 转 时间 日期 yyyy-mm-dd hh-mm-ss

    //格式化时间 var format = function(time, format){ var t = new Date(time); var tf = function(i){return (i ...

  5. bootstrap3 兼容IE8浏览器

    近期在使用bootstrap这个优秀的前端框架,这个框架非常强大,框架里面有下拉菜单.按钮组.按钮下拉菜单.导航.导航条.面包屑.分页.排版.缩略图.警告对话框.进度条.媒体对象等,bootstrap ...

  6. Asp.net MVC 4新项目中创建area的后续操作

    Asp.net MVC 4新项目中创建area后,往往HomeController与area的HomeController路由发生混淆,需要手工设置一些地方避免mvc无法识别默认路由的状况. 无废话具 ...

  7. Effective Java 67 Avoid excessive synchronization

    Principle To avoid liveness and safety failures, never cede control to the client within a synchroni ...

  8. ELK 信息统计分析-1

    Aggregations 格式如下: "aggregations"{ //可以简写为aggs "<aggregation_name>":{ //名称 ...

  9. 汇编中call printf参数压栈时错误理解

    EAX, ECX,EDX,EBX均可以32bit,16bit,8bit访问,如下所示: <-------------------EAX------------------------>|& ...

  10. nginx如何设置访问跳转到一个固定页面

    方法一 server {    listen 80 ;    server_name  www.xxx.com xxx.com;      index   test.html  index.html ...