本來想上周末沒能用DELPHI實現動態代理就算了
可是這幾天卻始終放不下這個想法
這實在是一個太美妙的想法了
而且在認真看了VCL對SOAP的實現後
現在至少有九成的把握可以實現這樣一個動態代理
那麼動態代理有什麼用?
這要先從GoF的Proxy模式說起
假設有下面這樣一個接口及其實現
現在
如果你是這個接口的用戶
而這個接口及其實現者提供了一個
Foo : IFoo;
給你
其中Foo指向TFooImpl的一個實例
現在你有了IFoo的定義
和這個Foo實例
注意
你沒有TFooImpl的定義和實現代碼
如果現在要求你為所有的IFoo
doSth增加事務功能(假設doSth被實現為對數據庫作更新操作)
要怎麼辦?
GoF的Proxy模式就是解決方案之一
如果所示
首先要實現一個新的IFoo接口實現
TStaticProxy
其中用了一個屬性FImpl記錄了TFooImpl的實例
然後在 TStaticProxy中實現doSth和bar
並且將不需要變更的bar函數直接委托給FImpl處理
而在doSth的實現裡加入事務處理即可
TStaticProxy的代碼大致如下
TStaticProxy = class( TInterfacedObject
IIFoo )
private
FImpl : IFoo;
public
constructor Create( aImpl : IFoo );
function doSth(
) : xxx;
function bar(
) : xxx;
end;
constructor TStaticProxy
Create( aImpl : IFoo );
Begin
FImpl := aImpl;
End;
function TStaticProxy
doSth(
) : xxx;
Begin
BeginTransaction;
Result := FImpl
doSth(
);
EndTransaction;
End;
function TStaticProxy
bar(
) : xxx;
Begin
Result := FImpl
bar(
);
End;
然後
在所有需要用到Foo對象的地方
改用新的NewFoo對象
如下
var
NewFoo : IFoo;
Begin
NewFoo := TStaticProxy
Create( Foo ) As IFoo;
// 之後就可以把NewFoo完全當作Foo一樣使用了
End;
可見
我們通過了一個Proxy類代理了所有對IFoo接口的操作
相當於在從IFoo到TFooImpl之間插入了自己的代碼
在某種程度上
這就是AOP所謂的
橫切
當然如果你能有TFooImpl的代碼的話
就簡單了
只要如下
從TFooImpl派生一個TNewFooImpl
然後在其中Override一下TFooImpl中的doSth即可
然後就創建TNewFooImpl的實例來代替Foo引用即可
但問題就在於你必須擁用TFooImpl的代碼
並且可以變更所提供的Foo實例
但這在很多時候是做不到的
除非不是用DELPHI
而是如 Python一類的動態語言^O^
比如組件容器
比如遠程實例等
還有像
虛代理
(就是當創建FImpl代價很高時
就在創建時只創建代理類
然後在真正需要時才創建FImpl的實例)
但上面這種靜態代理還是很麻煩
首先
如果IFoo的成員函數很多的話
必須要一一為它們加上代理實現
其次
如果在應用中有很多接口需要代理時
就必須一一為它們寫這樣的專用代理類
第三
需要變更代理功能時
需要修改所有的代理類……
特別是像組件容器或是通用遠程代理這樣
對要實現的接口並不能確定的情況下
靜態代理一點用也沒有
所以我們需要
動態代理
我是在看了GIGIX發表在今年第一期《程序員》上的《動態代理的前世今生》一文後
雖然他說是的JAVA在 JDK
中提出的
在java
lang
reflect中的proxy
但這卻讓我突發奇想
發現其實完全可以在DELPHI裡也實現這樣一個動態代理
一個典型的動態代理如下
這樣
我們只需要把要增加在功能做成一個IInvocationHandler接口的實例
如圖中的TFooInvHandler
然後動態創建一個支持IFoo接口的TDynamicProxy的實例
它是一個動態代理
只需要傳入相應的參數
要實現的接口和相應的InvHandler實例即可
不需要為每個接口寫一個代理
當然如GIGIX文中所說
對於C++來說
這個可以用模板實現
但問題在於模板歸根到底是一種編譯時的動態化技術
對於組件容器這樣需要運行時動態化的應用
它還是不能實現
最後
InvHandler通過RTTI去調用具體的實現Foo
它的用法大致如下
TFooInvHandler = class( TInterfacedObject
IInvocationHandler )
private
FImpl : IFoo;
public
constructor Create( aImpl : IFoo );
function Invoke( IID
MethodName
Args[] ) : xxx;
end;
constructor TFooInvHandler
Create( aImpl : IFoo );
Begin
FImpl := aImpl;
End;
function TFooInvHandler
Invoke( IID
MethodName
Args[] ) : xxx
Begin
If ( IID = IFoo ) AND ( MethodName =
doSth
) Then
Begin
BeginTransaction;
Result := DoInvoke( FImpl
IID
MethodName
Args[] );
EndTransaction;
End
Else
Result := DoInvoke( FImpl
IID
MethodName
Args[] );
End;
var
Handler : IInvocationHandler;
NewFoo : IFoo;
Begin
Handler := TFooInvHandler
Create( Foo );
NewFoo := TDynamicProxy
Create( TypeInfo(IFoo)
Handler ) As IFoo;
// 之後就可以把NewFoo完全當作Foo一樣使用了
End;
注意
其中IInvocationHandler接口我還沒想好要怎麼定義
所以這段代碼只是大致說明一下問題
另外
其中的DoInvoke就是通過RTTI來調用FImpl的
從上面的代碼可以看到
TDynamicProxy通過參數IFoo動態生成了一個對IFoo接口的代理
並且通過Handler參數插入一個處理接口IInvocationHandler
由TDynamicProxy把對IFoo接口的調用全部轉成對IInvocationHandler接口的調用
最後由TFooInvHandler類來視情況處理
在這裡
可以通過運行時配置方式來動態決定是否需要切入事務所處理
需要對哪個接口的哪個方法切入
有了這樣一個動態代理
還可以很方便地在InvocationHandler裡切入如安全性檢查
LOG等
這樣的話
用DELPHI來實現AOP也不成問題了
現在我面臨的問題就是
如何來定義這個IInvocationHandler
其實這裡最主要的問題就是參數的傳遞的問題
接口可以用IID表示
方法可以用方法名
但參數變化太多了
一是數量不確定
可以有任意多個參數
二是類型不確定
三是傳值參數和引用參數的問題
如前面那個例子用的是簡單的辦法
就是用一個不定長的Variant數組來記錄
可以解決前兩個問題
但第三個問題就比較麻煩
難道要用一個Tuple來作返回值?太麻煩了吧
在VCL的SOAP實現裡是通過一個TInvContext在記錄的
但這樣的話對於Handler的開發者來說
就不得不面對TInvContext的內部復雜性
易用性太差
From:http://tw.wingwit.com/Article/program/Delphi/201311/8416.html