Delphi的基于接口(IInterface)的多播监听器模式(观察者模式 )
本文来自: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)的多播监听器模式(观察者模式 )的更多相关文章
- Eclipse 基于接口编程的时候,快速跳转到实现类的方法(图文)
Eclipse 基于接口编程的时候,要跳转到实现类很麻烦,其实Eclipse已经实现该功能. 只要按照Ctrl键,把鼠标的光标放在要跳转的方法上面,第一个是跳转到接口里面,第二个方法是跳转到实现类的位 ...
- mybatis整合spring 之 基于接口映射的多对一关系
转载自:http://my.oschina.net/huangcongmin12/blog/83731 mybatis整合spring 之 基于接口映射的多对一关系. 项目用到俩个表,即studen ...
- 【java爬虫】---爬虫+基于接口的网络爬虫
爬虫+基于接口的网络爬虫 上一篇讲了[java爬虫]---爬虫+jsoup轻松爬博客,该方式有个很大的局限性,就是你通过jsoup爬虫只适合爬静态网页,所以只能爬当前页面的所有新闻.如果需要爬一个网站 ...
- 基于接口回调详解JUC中Callable和FutureTask实现原理
Callable接口和FutureTask实现类,是JUC(Java Util Concurrent)包中很重要的两个技术实现,它们使获取多线程运行结果成为可能.它们底层的实现,就是基于接口回调技术. ...
- was集群下基于接口分布式架构和开发经验谈
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/luozhonghua2014/article/details/34084935 某b项目是我首 ...
- Net系列框架-Dapper+AutoFac 基于接口
Net系列框架-Dapper+AutoFac 基于接口 工作将近6年多了,工作中也陆陆续续学习和搭建了不少的框架,后续将按由浅入深的方式,整理出一些框架源码,所有框架源码本人都亲自调试通过,如果有问题 ...
- 最简单的动态代理实例(spring基于接口代理的AOP原理)
JDK的动态代理是基于接口的 package com.open.aop; public interface BusinessInterface { public void processBus ...
- Spring AOP 介绍与基于接口的实现
热烈推荐:超多IT资源,尽在798资源网 声明:转载文章,为防止丢失所以做此备份. 本文来自公众号:程序之心 原文地址:https://mp.weixin.qq.com/s/vo94gVyTss0LY ...
- 配置基于接口地址池的DHCP
配置基于接口地址池的DHCP 原理概述 DHCP(动态主机配置协议),采用C/S方式工作,C向S动态请求配置信息,S自动分配配置信息. 基于接口地址池的DHCP服务器,链接这个接口网段的用户都可以从该 ...
随机推荐
- Quartz 2d绘图
今天看了一下Quartz 2D绘图,我只想说:不要把绘图和动画那些东西当做一个很复杂的东西,其实只要你认真看还是可以理解的.他们并不难.啰嗦了几句,现在直接进入正题: 前提是我们必须新建一个singl ...
- 【读书笔记】iOS-忽略编译警告
一,忽略编译警告的命令. -w 禁止掉所有的编译警告. -Wno-unused-variable 只禁止掉未使用的变量的编译警告. 二,忽略编译警告的方法. targets--->Buil ...
- 我为什么用 SQLite 和 FMDB 而不用 Core Data
凭良心讲,我不能告诉你不去使用Core Data.它不错,而且也在变好,并且它被很多其他Cocoa开发者所理解,当有新人加入你的组或者需要别人接手你的项目的时候,这点很重要.更重要的是,不值得花时间和 ...
- .net程序员工作两年总结
(2015年9月) 最近换了工作,面试了很多家公司想总结下,以便以后回顾知道自己是怎么走过来的. 入行背景: 我是半路转行做软件开发的,2011年7月大学专科毕业,大学专业是:机械制造及其自动化:20 ...
- 匿名PL/SQL
立此存照 匿名PL/SQL 语法结构:PL/SQL是一种块结构的语言,组成PL/SQL程序的单元是逻辑块,一个PL/SQL程序包含了一个或多个逻辑块,每一块都可以划分3个部分.变量在使用前必须声明,P ...
- oracle-表空间满了
一.查看 "sum MB", (a.bytes "used MB",b.bytes "free MB", ,) "used%&qu ...
- Android开发之 Android应用程序目录结构解析
建立的HelloWorld的应用项目,其代码是由ADT插件自动生成的,形成Android项目特有的结构框架. 接下来让我带领大家解析一个Android程序的各个组成部分,这次我们拿一个Hello,Wo ...
- 续Gulp使用入门三步压缩CSS
gulp 压缩css 一.安装 gulp-minify-css 模块 提示:你需要使用命令行的 cd 切换到对应目录后进行安装操作. 在命令行输入 npm install gulp-minify-cs ...
- 给Apache增加SSI支持(shtml的奥秘)
什么是SSI? SSI是英文Server Side Includes的缩写,翻译成中文就是服务器端包含的意思.从技术角度上说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针.SSI具有强大 ...
- js控制iframe跳转
网页:<iframe src="" id="iframe_a" name="iframe_a" style="width:1 ...