本文来自: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. android XMl 解析神奇xstream 二: 把对象转换成xml

    前言:对xstream不理解的请看:android XMl 解析神奇xstream 一: 解析android项目中 asset 文件夹 下的 aa.xml 文件 1.Javabeen 代码 packa ...

  2. Android项目实战(十):自定义倒计时的TextView

    项目总结 -------------------------------------------------------------------------------------------- 有这 ...

  3. 弃用的同步get和post请求

    #import "ViewController.h" #import "Header.h" @interface ViewController () <N ...

  4. ShellExecuteA()&MessageBoxA()

    #include<windows.h> #include<stdlib.h> void main() { ) { ShellExecuteA(, , , ); //0代表系统启 ...

  5. Linux线程学习(一)

    一.Linux进程与线程概述 进程与线程 为什么对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因为,线程共享相同的内存空间.不同的线程可以存取内存中的同一个变量.所以,程序中的所有线程都可 ...

  6. nginx 与 lua 开发环境搭建

    首先下载最新版的 相关软件 的安装文件. nginx: http://nginx.org/en/download.html LuaJIT: http://luajit.org/download.htm ...

  7. SiteMesh, SpringMVC, Shiro 配置

    1. 首先在在web.xml文件中,加入SiteMesh和shiro的过滤器,保证SiteMesh的过滤器配置放在shiro的过滤器后面,不然的话,shiro的标签不能正确处理. <?xml v ...

  8. 软件测试作业3--Junit、hamcrest、eclemmat的安装和使用

    1.   how to install junit, hamcrest and eclemma? 首先下载下来Junit和Hamcrest的jar包,然后新建项目的时候将这两个jar包导入到工程里面就 ...

  9. jQuery Validate 表单验证插件----通过name属性来关联字段来验证,改变默认的提示信息,将校验规则写到 js 代码中

    一.下载依赖包 网盘下载:https://yunpan.cn/cryvgGGAQ3DSW  访问密码 f224 二. 添加一个另外一个插件jquery.validate.messages_cn.js. ...

  10. 续Gulp使用入门三步压缩图片

    gulp 压缩图片 压缩 图片文件可降低文件大小,提高图片加载速度. 找到规律转换为 gulp 代码 规律 找到 images/ 目录下的所有文件,压缩它们,将压缩后的文件存放在 dist/image ...