本文来自: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. Sharepoint 2013 创建TimeJob 自动发送邮件

    创建Time Job 继承继承SPJobDefinition 并且实现里边的 Execute方法 部署 可以手动部署,把程序集放到GAC,手动激活feature 如果部署的时候说feature已经存在 ...

  2. iOS设计模式之原型模式

    原型模式 基本理解 原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节 ...

  3. 【读书笔记】iOS-Objective-C对C的扩展基础知识

    一,Xcode的.m扩展名表示文件含有Objective-C代码,应由Objective-C编译器处理.C编译器处理名称以.c结尾的文件,而C++编译器处理.cpp文件.在Xcode中,所有这些编译工 ...

  4. 【原】iOSCoreAnimation动画系列教程(一):CABasicAnimation【包会】

    本文的最新版本已经发布在简书[编程小翁]上,强烈建议到上查看简书,[点击这里跳转]. 在iOS中,图形可分为以下几个层次: 越上层,封装程度越高,动画实现越简洁越简单,但是自由度越低:反之亦然.本文着 ...

  5. iOS之UI--CAShapeLayer

    关于CAShapeLayer 内容大纲: CAShapeLayer简介 贝塞尔曲线与CAShapeLayer的关系 strokeStart和strokeEnd 动画 用CAShapeLayer实现进度 ...

  6. 捡火柴的Nova君(n个线段相交问题)

    题目来源:https://biancheng.love/contest-ng/index.html#/41/problems 捡火柴的Nova君 题目描述 南方没暖气,怕冷的的宝宝们只能用火柴取暖.然 ...

  7. CentOS 7 修改时区(转)

    本文转载至:http://mathslinux.org/?p=637 Linux 系统(我特指发行版, 没说内核) 下大部分软件的风格就是不会仔细去考虑向后 的兼容性, 比如你上个版本能用这种程序配置 ...

  8. 计算几何 平面最近点对 nlogn分治算法 求平面中距离最近的两点

    平面最近点对,即平面中距离最近的两点 分治算法: int SOLVE(int left,int right)//求解点集中区间[left,right]中的最近点对 { double ans; //an ...

  9. uniq

    -c, --count 在每行前加上表示相应行目出现次数的前缀编号-d, --repeated 只输出重复的行-D, --all-repeated[=delimit-method 显示所有重复的行de ...

  10. paypal IPN 接口返回INVALID参数可能问题

    工作于浏览器Chrome时,登录IPN Simulator页发送测试数据,payment_date栏位值中出现乱码,导致无法返回正确的VERIFIED,在此记录.