我的AOP For Delphi Frame - MeAOP 发布了(100分)

  • 主题发起人 主题发起人 riceball
  • 开始时间 开始时间
R

riceball

Unregistered / Unconfirmed
GUEST, unregistred user!
=>著者:Riceball LEE>=

声 明
一、关于在Internet 网上转发本文件的声明
任何个人或者机构可以在 Internet 上自由的转发和引用。但转发过程中必须保证本文的完整性,不得进行任何的修改。并需注明出处!作者允许任何个人或者机构在本文的转发过程中附带其它的文件来描述、补充或者评价本文。但不得在本文的原文中修改。

二、关于在传统媒体上刊载、使用、引用本文件的声明
禁止在未经本人书面(含电子邮件) 许可的情况下在传统媒体上刊载、使用、引用文件的部分或全部内容。如果确需刊载、使用、引用本文件, 请用 EMail 与本人联系。


== 面向方面编程思想简介 ==
Summary:面向方面编程,正确的理解应该说是面向功能的一种编程方法。它作为对象建模的辅助,目的就是为了重用功能:

1、将通用功能从不相关类之中分离出来;
2、能够使得很多类共享一个功能,一旦功能发生变化,不必修改很多类,只要修改这个功能就可以了。

因此,我认为老外的定义并不妥当,故,我这里将其称之为 Feature 而不是 Aspect.
也许说成是 Featrure Oriented Programming 更为恰当。

在我看来,它实现的核心思想就是对类(对象)的方法的拦劫和注入。

=== 举例 ===

举例:假设有在一个应用系统中,有一个共享的数据必须被并发同时访问,首先,将这个数据封装在数据对象中,称为Data Class,同时,将有多个访问类,专门用于在同一时刻访问这同一个数据对象。

为了完成上述并发访问同一资源的功能,需要引入锁Lock的概念,也就是说,某个时刻,当有一个访问类访问这个数据对象时,这个数据对象必须上锁Locked,用完后就立即解锁unLocked,再供其它访问类访问。

使用传统的编程习惯,我们会创建一个抽象类,所有的访问类继承这个抽象父类,如下:

<code>
TCustomVisiter = Class
protected
  procedure Locked();virtual;abstract;
  procedure Unlocked();virtual;abstract;
  procedure AccessDataObject();virtual;
end;
}
</code>

缺点:

* AccessDataObject()方法需要有“锁”状态之类的相关代码。
* Delphi只提供了单继承,因此具体访问类只能继承这个父类,如果具体访问类还要继承其它父类,比如另外一个如Visiter的父类,将无法方便实现。
* 重用被打折扣,具体访问类因为也包含“锁”状态之类的相关代码,只能被重用在相关有“锁”的场合,重用范围很窄。

仔细研究这个应用的“锁”,它其实有下列特性:
* “锁”功能不是具体访问类的首要或主要功能,访问类主要功能是访问数据对象,例如读取数据或更改动作。
* “锁”行为其实是和具体访问类的主要功能可以独立、区分开来的。
* “锁”功能其实是这个系统的一个纵向切面,涉及许多类的方法。

因此,一个新的程序结构应该是关注系统的纵向切面,例如这个应用的“锁”功能,这个新的类就是 Feature(功能)

在这个应用中,“锁”这个功能(Feature)应该有以下职责:
  * 对被访问对象实现加锁或解锁功能。
* 保证所有在修改数据对象的操作之前能够调用lock()加锁,在它使用完成后,调用unlock()解锁。

“锁”这个功能(Feature)类似于如下的代码:
<code>
TLockFeature = Class(TMeCustomFeature)
protected
procedure Locked();virtual;
procedure Unlocked();virtual;
function Islocked(): Boolean;virtual;
protected
function AllowExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem): Boolean;override;
procedure AfterExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem);override;
end;

function TLockFeature.AllowExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem): Boolean;
begin
Result := not IsLocked
//only not isLocked can be allow execute.
if Result then Locked;
end;

procedure TLockFeature.AfterExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem);
begin
Unlocked;
end;
</code>

设计好“锁”这个功能(Feature)后,只要将“锁”这个功能(Feature)添加你希望的类的相应方法上,该类的方法就自动加上了锁机制。
<code>
//将“锁”这个功能(Feature)添加到我们前面提到的TCustomVisiter.AccessDataObject
TLockFeature.AddTo(TCustomVisiter, @TCustomVisiter.AccessDataObject);
</code>

值得注意的是,由于我使用的是代码直接注入方式,所以“锁”这个功能(Feature)将只影响具体注入的那个类的方法,
对于其派生类不能施加影响,除非是在派生类的方法中调用了 inherited 方法。解决办法由二:
方法1:规定在派生类的方法中必须调用父类的方法:
<code>
TMyVisiter = Class
protected
  procedure AccessDataObject();override;
end;

procedure TMyVisiter.AccessDataObject();
begin
inherited AccessDataObject();
.....
end;

</code>

方法2:将其添加到每一个具体需要该功能的类上。

<code>
TLockFeature.AddTo(TMyVisiter, @TMyVisiter.AccessDataObject);
TLockFeature.AddTo(TOtherVisiter, @TOtherVisiter.AccessDataObject);
</code>

=== AOP应用范围 ===
凡是能够将功能抽象出来,独立于对象存在的,都是AOP的应用范畴:

|| * Authentication || 权限功能 ||
|| * Caching || 缓存 ||
|| * Context passing || 内容传递 ||
|| * Error handling || 错误处理 ||
|| * Lazy loading   || 懒加载 ||
|| * Debugging   || 调试 ||
|| * logging, tracing || 记录跟踪 ||
|| * profiling || 性能评估 ||
|| * monitoring  || 监视 ||
|| * Performance optimization || 性能优化 ||
|| * Persistence   || 持久化 ||
|| * Resource pooling  || 资源池 ||
|| * Synchronization  || 同步 ||
|| * Transactions || 事务 ||
|| * RPC || 远程过程方法调用 ||

== My AOP For Delphi Frame - MeAOP ==
我的 AOP For Delphi Frame 是基于代码注入,核心也就是黑客的技能,对原始代码进行注入替换——通过对过程方法注入代码完成对方法的拦截处理!我对基于动态代理(对对象类进行包裹)的AOP形式不敢兴趣,这样性能太低,而代码注入则是速度最快的解决方案,内存开销也比动态代理小多了。

我希望我的AOP架构简单高效并且易于使用。随便提一句,我把我的AOP叫做MeAOP.

在MeAOP中,你不必去想什么joint, advise, cross-cut 等等莫名的概念。你只需要知道功能这个概念,
MeAOP的核心就是抽象出了定制功能类,你所做的开发就是围绕开发自己特有的功能类展开的。
你只要简单的调用 TMeCustomFeature 的类函数 AddTo 就可以将功能添加到你指定的类的方法上。 eg,
<code>
TMyFeature.AddTo(aClass, @aClass.aMethod, 'aMethod').
</code>

那么如何设计自己的功能类呢?
很简单,只需要在你的功能类中重载你需要拦截的位置的方法即可,如:BeforeExecute, AfterExecute 等等。
你的所有的功能类都应该从 TMeCustomFeature 派生.

注意:[Ver0.5] 现目前版本我只是实现了对于无参数的过程和方法(不包括有返回值函数)的支持。

<code>
TMyFeature = class(TMeCustomFeature)
protected
{: 仅当返回为真才执行拦截的方法 MethodItem }
{ 如果你没有重载,它总是返回真.}
function AllowExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem): Boolean;override;
{: 在MethodItem被执行前被触发,发生在AllowExecute返回为真后 }
procedure BeforeExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem);override;
{: 在MethodItem被执行后被触发(即使MethodItem引发了异常也依然会被触发) }
procedure AfterExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem);override;
{: 执行MethodItem的过程中引发了异常后被触发。}
procedure AfterException(Sender: TObject
MethodItem: TMeInterceptedMethodItem
E: Exception);override;
end;
</code>


=== MeAOP 架构 ===
我的AOP 架构大致分为三层:
1.底层核心:过程方法注入器: 提供最简单最轻巧的注入器(一个注入器大约仅占内存32个字节)
2.中层核心:则是过程方法拦截器
2.1. 只支持无参数的过程方法拦截器
2.2. 支持带参数的过程方法拦截器
3.上层核心:提供面向功能的编程类库,最终实现能够将很容易将新的功能附加到指定类的方法上。


==== 底层核心(注入器) ====
包括底层的函数库和注入器。

===== 函数库 =====

单元: uMeTypInfo

提供了对 VMT(虚方法表), DMT(动态方法表), PMT(发布的方法表) 表的类型结构,以及访问相应函数。
提供对各类方法的地址查找访问的函数。
提供直接读取修改程序内存的函数。

状态:已完成(初测通过:仅限于我用到的函数)。

===== 过程方法注入器:TMeInjector =====

TMeInjector 提供最简单最轻巧最易于使用维护的注入器(一个注入器大约仅占内存36个字节)。

状态:已完成(初测通过)。

==== 中层核心(拦截器) ====

===== 基本拦截器 =====

是无参数的过程方法拦截器,它是最底层的拦截器原型抽象,只支持无参数的方法和过程,是AOP的核心类。

状态:已完成(基本测试通过)。

===== 拦截器 =====

支持带参数的过程方法拦截器

状态:设计构思阶段。
难点:参数,调用约定

==== 上层核心(面向功能类库) ====

提供面向功能的编程类库,最终实现能够将很容易将新的功能附加到指定类的方法上。

状态:设计构思阶段。

===== TMeCustomFeature =====

这是 MeAOP 的核心类,所有的功能类都应该从 TMeCustomFeature 派生.

在MeAOP中,你不必去想什么joint, advise, cross-cut 等等莫名的概念。你只需要知道功能这个概念,
MeAOP的核心就是抽象出了定制功能类,你所做的开发就是围绕开发自己特有的功能类展开的。
你只要简单的调用 TMeCustomFeature 的类函数 AddTo 就可以将功能添加到你指定的类的方法上。 eg,
<code>
TMyFeature.AddTo(aClass, @aClass.aMethod, 'aMethod').
</code>

<code>
TMeCustomFeature = class
protected
{: 仅当返回为真才执行拦截的方法 MethodItem }
{ 如果你没有重载,它总是返回真.}
function AllowExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem): Boolean;override;
{: 在MethodItem被执行前被触发,发生在AllowExecute返回为真后 }
procedure BeforeExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem);override;
{: 在MethodItem被执行后被触发(即使MethodItem引发了异常也依然会被触发) }
procedure AfterExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem);override;
{: 执行MethodItem的过程中引发了异常后被触发。}
procedure AfterException(Sender: TObject
MethodItem: TMeInterceptedMethodItem
E: Exception);override;
public
{: Add the feature to a procedure. }
class function AddTo(aProc:Pointer
const aProcName: string): TMeCustomInterceptor
overload;
{: Add the feature to a method. }
class function AddTo(aClass: TClass
aMethod: Pointer
const aMethodName: string): TMeCustomInterceptor
overload;
{: Add the feature to a published method. }
class function AddTo(aClass: TClass
aMethodName: String): TMeCustomInterceptor
overload;
{: remove the feature from the method or procedure. }
class function RemoveFrom(aClass: TClass
aMethod: Pointer): Boolean;overload;
{: remove the feature from the published method. }
class function RemoveFrom(aClass: TClass
aMethodName: String): Boolean
overload;
end;
</code>

那么如何设计自己的功能类呢?
很简单,只需要在你的功能类中重载你需要拦截的位置的方法即可,如:BeforeExecute, AfterExecute 等等。

<code>
TMyFeature = class(TMeCustomFeature)
protected
{: 仅当返回为真才执行拦截的方法 MethodItem }
{ 如果你没有重载,它总是返回真.}
function AllowExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem): Boolean;override;
{: 在MethodItem被执行前被触发,发生在AllowExecute返回为真后 }
procedure BeforeExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem);override;
{: 在MethodItem被执行后被触发(即使MethodItem引发了异常也依然会被触发) }
procedure AfterExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem);override;
{: 执行MethodItem的过程中引发了异常后被触发。}
procedure AfterException(Sender: TObject
MethodItem: TMeInterceptedMethodItem
E: Exception);override;
end;
</code>


===== 远程方法类库 =====

为指定类的抽象方法,添加远程执行功能。

状态:设计构思阶段。

=== MeAOP 下载 ===
Current Version: 0.5.0.0
+ 实现了对于无参数的过程和方法(不包括有返回值函数)的支持。


* http://dev.cq118.com/UploadFiles/attachments/Delphi/AOP/Readme/MeAOPv05-1.rar (no src, D7 D10)

欢迎测试。我下周末才能来瞧瞧,如果有反馈的话。
 
厉害,有点意思,关注。

希望不要弄得太复杂,否则就没有实用价值了。支持更简单的方式
 
正是我需要的,学习
 
高,不错.
 
高人,先学习学习!
 
不要弄些将简单问题复杂化并且没有钱途的东西。
 
好东东,值得研究一下!
 
如果你不懂OO编程,不知道调用约定,不懂得线程,最好别玩这个。

鉴于一个礼拜过去了,我没有看到一点关于我的AOP反馈信息:错误报告,应用等等。
所以无源码的新版本就不对外公布了,只有那些给了我有价值反馈信息,才有资格免费
获得新版本。

注意[0.5.2.0]:
!* 只支持register调用约定(Delphi的默认调用约定)的无参数函数
!* 不支持多线程(拦截的过程方法)
!* 不支持拦截构造方法(Constructors)和析构方法(Destructors)
!* 过程体内容必须不小于5个字节(因为我的注入代码为5字节)

版本历史:
Ver 0.5.2.0
+ 无参数的函数返回值现在支持任何类型了!
* [Bug] minor bug fixed.

Ver 0.5.1.0
+ 支持无参数函数
+ 支持拦截在DLL中的函数和方法
* [Bug] minor bug fixed.

Ver 0.5.0.0 [Released]
+ First Released.
+ intercept the procedure and method with no parameters.

我目前的开发计划 milestone:
Ver 0.6 - 定义类型的 RTTI 信息类,以及相应的管理函数。
基本类型信息,过程参数信息,管理用户自定义类型
Ver 0.7 - 支持带参数的过程方法,但是不可以修改任何参数。(针对 Register 调用约定)
支持对 Constructor 和 Destructor 方法的拦截。
Ver 0.8 - 支持带参数的过程方法,可以修改参数值(包括函数返回值)。
如果是在执行前拦截,这样就可以实现对参数值的约束。
如果是在执行后拦截,则就可以实现对返回值的约束。
这是一把双刃剑,如果滥用的话,就等于是对过程的劫持。
Ver 0.9 - 支持虚方法和动态方法的重入——多线程支持
Ver 1.0 - 对面向功能类(TMeCustomFeature)制定开发规范
规范:拦截的方法必须是发布的动态(dynamic)或虚方法,并且该方法名称不得以&quot;_&quot;或小写字母&quot;i&quot;打头.
如果方法是带参数的活函数,那么必须注册其过程类型!
能够一次性添加某类中符合规范的方法。
支持 stdcall 调用约定的过程或方法
支持 pascal 调用约定的过程或方法
 
// 对于面向方面编程不是很了解,稍微说说我的看法。。
== 面向方面编程思想简介 ==
Summary:面向方面编程,正确的理解应该说是面向功能的一种编程方法。它作为对象建模的辅助,目的就是为了重用功能:

1、将通用功能从不相关类之中分离出来;
2、能够使得很多类共享一个功能,一旦功能发生变化,不必修改很多类,只要修改这个功能就可以了。

//我觉得,其实Delphi典型的PFM模型中 Event就可为了这个目的很好的工作。上面你的例子
TCustomVisiter = Class
protected
  procedure Locked();virtual;abstract;
  procedure Unlocked();virtual;abstract;
  procedure AccessDataObject();virtual;
end;
如果我写的话(按注入需要)是:
TCustomVisiter = Class
...
protected
  procedure AccessDataObject();virtual;
public
procedure ExecuteAccessData;
property OnLock: TNofityEvent read F% write F%
property OnUnlock: TNofityEvent wred F% write F%;
end;

procedure TCustomVisiter.ExecuteAccessData;
begin
OnLock;
try
AcessDataObject;
finally
OnUnlock;
end;
end;
如果更加的灵活的话 就添加 Lock 和 Unlock 的 virtual 方法,封装 On事件。具体可参照 TCanvas 的源码。

至于共享功能的话,如果使用过如
procedure TForm1.ButtonClick(Sender: TObject);
begin
ShowMessage(TButton(Sender).Name);
end;
这样的代码就应该比较熟悉了。

基本来说 OO 对于我来说已经比较足够了。(当然 Delphi 对接口和GC 的支持是有不足,如果真的需要比较丰富的功能和好的规范,是应该使用接口的),使用诸如 拦截 的话对理解性来说比较差,暂时就不需要。
 
to jeffrey_s
这点上你悟性不坏,不错,你已经把握住了 AOP 实质上的一些东西,从某个方面说Event的确可以看作是AOP,毕竟AOP只是一种方法论。
它能够解决功能复用中的一部分问题,但是,你想得还不够多,我就你写的事件模型问你些问题,权作提醒,如果
要在上面加上日志功能呢?日志功能至少要记录是谁调用的吧。

然后还要加上数据缓冲池功能呢?然后如果我们还要再加功能,夸张一点,如果这上面最后被加上了99个功能呢?

使用 Interface=良好的规范? 使用 Interface = 丰富的功能?
至于利用接口和代理机制的,可以看看Delphi的WebService。

当然你设想的事件模型,其实还可以再改进,部分解决n个功能添加的问题!我就直接用代码说话,懒得解释了:

TCustomVisiter = class
procedure BeforeExecute;virtual;
procedure AfterExecute;virtual;
procedure AccessDataObject();virtual;
property OnBeforeExecute;
property OnAfterExecute;
end;

procedure TCustomVisiter.AccessDataObject();
begin
BeforeExecute;
try
....
finally
AfterExecute;
end;
end;

无论哪一种AOP实现最终都要落实到对方法的拦截插入上!只不过实现的方法不同而已。

试问,不拦截怎么插入功能?就算是用事件,虽说不用单独实现拦截,但是将FOnLock语句插入到方法中去,这其实也可以当作是一种拦截了(拦截了方法的开始和结束)!

而且功能的添加也很容易看懂:
TLockingFeature.AddTo(TCustomVisiter, @TCustomVisiter.AccessDataObject);

这不就是将“锁”功能添加到TCustomVisiter类的AccessDataObject方法上的意思么。
 
由于兴趣等关系,至今还没看Delphi的WebService,惭愧。

对于接口,我指的是方便管理所注入的 Event 方面。例:
IRecordLog = interface
property OnRecordLog: TNotifyEvent;
end;
加入时:
procedure TLogMgr.Add;
var
Obj: TObject;
begin
for Obj in ListToMgr do
if Support(Obj, IRecordLog) then
Obj.OnRecordLog := MyLogEvent;
end;
当然,这只是使用 OO 的方法,用 AOP 可能就不用。

如果按 AOP 的方法,在一定的情况下应该是比较有用的。比如 特殊化,单独测试 等的情况下,如:
TCustomVisiter = class
...
procedure AccessDataObject1
virtual;
procedure AccessDataObject2
virtual;
...
end;
虽然 AccessDataObject1 和 AccessDataObject2 可以都使用 BeforeExecute 等的语句都插入,但 个别处理 AccessDataObject1 或 AccessDataObject2 时使用 OO 就显得相当复杂和累赘,显得不够优雅。 AOP 就是用 主动处理 代替 逻辑判断,有时候反而显得简洁。
我的理解大概是这样。

不过使用 AOP 拦截后,可读行就下降了不少。比如有这样的简单公用方法:
procedure TForm1.ButtonClick(Sender: TObject);
begin
ShowMessage(TButton(Sender).Name)

end;
但在别的地方这样拦截:
function TLockFeature.AllowExecute(Sender: TObject
MethodItem: TMeInterceptedMethodItem): Boolean;
begin
ShowMessage('Button1');
Result := False

end;
如果有更复杂的,我想一定可以把那个测试或维护的气得半活。
 
看样子,你还没有能够理解我问的意思,我的意思是功能的复用的意义还在于:
让功能开发完全从对象中独立出来,缩短开发周期,增强代码可读性
能够在完全不改动原有的类的方法的情况下,为其添加新的功能
让开发者能够全神贯注于该方法本身的功能上,不用考虑其它任何附加东西!!
而其它开发小组也能集中精力只关注与自己的目标,如,日志功能,数据缓冲池功能。
这样,软件开发才能进行并行的流水作业!各个功能由不同的开发组开发测试,并由
各个开发组保证其功能的完备以及完整测试。这样剩下的就是组装的问题了。

试想,AccessDataObject开发小组只将精力集中在如何使得AccessDataObject本身更加完善,
而根本不用去考虑什么日志,加锁,数据缓冲等等与该方法无关的功能。在AccessDataObject
的代码中也根本就不会出现与之无关的功能代码。这样做,到底是可读性高了,还是低了呢?

就你举的例子来说,这个应该是想共享代码片断,那么它应该是被提炼成一个函数,怎么可能是功能?这里的功能都是对象啊!
当然如果你想说的是AOP的滥用问题,不仅仅是对AOP, OO也存在一个滥用问题。这样的滥用,的确将给开发带来很大的麻烦,
不但不能享受到开发的好处,还会使得软件变成豆腐渣工程,难以维护和更新!对于任何一种方法论,没有理解到它的意义,
而盲目乱用,那么只能是自尝苦果:比如这里把一个函数硬是弄成了一个功能对象。任何方法,都是有它的适用范畴和局限的。
AOP的目的是为了抽象功能对象,不是为了拦截,拦截只是手段,不是目的!!

代码的复用,首先考虑的是提炼成函数,不行才考虑对象(Object),然后才是类(Class)。要知道越到后面占用的内存越多,
效率也越低(VMT, DMT)。

AOP普遍存在的一个问题就是要想对装配好功能后的软件进行跟踪调试将变得比较困难。当然只会选好断点,那也好办。
不过如果开发组严格按照软件工程的要求,对每个类和功能部件事先进行了周密测试,只要你选用的AOP开发工具包没
有问题,那么装配后就不会出现问题。
 
“能够在完全不改动原有的类的方法的情况下,为其添加新的功能”

这好像是系统维护的一种比较好的方法,开发中如何应用,希望能看到更详细的例子说明,谢谢了
 
楼主,我很关注啊,等得花儿也谢了,要不EMail联系探讨?
 
废话少说,有没有DEMO来试试的?
 
例子在包中.

有两个例子:
demo1.dpr 展示AOP 编写的登录功能和日志功能(在日志中的RunProc修改了函数的参数以及返回值)
demo2.dpr 展示了 API 注入功能,拦截Windows API MessageBox, 如果加上线程的远程注入(嘿嘿,不过这是hacker的事情,在这里就不公布)
 
后退
顶部