熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Delphi編程 >> 正文

Delphi中關於動態代理應用技巧

2022-06-13   來源: Delphi編程 
本來想上周末沒能用DELPHI實現動態代理就算了可是這幾天卻始終放不下這個想法這實在是一個太美妙的想法了而且在認真看了VCL對SOAP的實現後現在至少有九成的把握可以實現這樣一個動態代理
  
  那麼動態代理有什麼用?
  這要先從GoF的Proxy模式說起
  
  假設有下面這樣一個接口及其實現
  
 

  現在如果你是這個接口的用戶而這個接口及其實現者提供了一個
  
  Foo : IFoo;
  
  給你其中Foo指向TFooImpl的一個實例現在你有了IFoo的定義和這個Foo實例注意你沒有TFooImpl的定義和實現代碼如果現在要求你為所有的IFoodoSth增加事務功能(假設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 TStaticProxyCreate( aImpl : IFoo );
  Begin
  FImpl := aImpl;
  End;
  
  function TStaticProxydoSth( ) : xxx;
  Begin
  BeginTransaction;
  Result := FImpldoSth( );
  EndTransaction;
  End;
  
  function TStaticProxybar( ) : xxx;
  Begin
  Result := FImplbar( );
  End;
  
  然後在所有需要用到Foo對象的地方改用新的NewFoo對象如下
  
  var
  NewFoo : IFoo;
  Begin
  NewFoo := TStaticProxyCreate( 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中提出的在javalangreflect中的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 TFooInvHandlerCreate( aImpl : IFoo );
  Begin
  FImpl := aImpl;
  End;
  
  function TFooInvHandlerInvoke( 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 := TFooInvHandlerCreate( Foo );
  NewFoo := TDynamicProxyCreate( 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
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.