那么動態代理有什么用?
這要先從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在 JDK1.3中提出的,在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[] ) : xxxBegin 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的內部復雜性,易用性太差。
這就是我現在還不能確定實現的那一成。-_-|||
新聞熱點
疑難解答