動態代理 — 代理模式的最高境界

  和往常一樣,小吳最后一個來到工位上,用腳點開主機的按鈕,伴隨著主機箱里傳出的卡車啟動般的轟轟聲,一天的快樂摸魚時光又開始了......

  點開騰訊體育新聞,小吳正準備看看昨晚NBA的戰況如何。突然,小吳的耳朵一陣警覺,似曾相識的腳步聲越來越近,,,小吳心想:難道,我上班摸魚被老板發現了??,,關掉手機正在播放的NBA精彩十佳球,小吳若無其事地緩緩抬起頭,把目光挪向眼前的電腦屏幕,眉間一皺,左手鍵盤,右手鼠標,毫無破綻,儼然一副認真搬磚的樣子。

  腳步聲隨即不出所料的停在小吳的跟前,抬頭一瞧,原來是項目經理大勇,小吳明白這是來活了。

  “小吳,客戶有個新需求,XX項目客戶需要記錄每一個業務操作的時間和操作人信息,你來做一下這個功能吧,客戶希望明天就能上線這個功能。”

  “哦。。哦。。。好的。”,小吳知道今天注定不會是快樂摸魚的一天,但是為了盡快能摸上魚,小吳快速的打開了XX項目,開始分析需求......

  項目的原始業務代碼是這樣的:

 1     //業務類接口
 2     public interface IBLLClass
 3     {
 4         void DoThing1();
 5         void DoThing2();
 6     }
 7 
 8     //業務類
 9     public class BLLClass : IBLLClass
10     {
11         public void DoThing1()
12         {
13             //DoThing1的業務邏輯
14             Console.WriteLine("執行DoThing1...");
15         }
16 
17         public void DoThing2()
18         {
19             //DoThing2的業務邏輯
20             Console.WriteLine("執行DoThing2...");
21         }
22         
23     }

  如果是一年前的小吳看到這樣的代碼,二話不說,他會直接往業務類中硬生生地插入要增加的功能,就像這樣:

 1     //業務類
 2     public class BLLClass : IBLLClass
 3     {
 4         public void DoThing1()
 5         {
 6             LogUserOperation();
 7 
 8             //DoThing1的業務邏輯
 9         }
10 
11         public void DoThing2()
12         {
13             LogUserOperation();
14 
15             //DoThing2的業務邏輯
16         }
17         
18         private void LogUserOperation()
19         {
20             //記錄每一個業務操作的時間和操作人信息
21         }
22     }

  但是,這時的小吳腦子里想到的是他在某一本講設計模式的書上看到的那句話:盡量避免編寫對原系統代碼侵入性強的代碼。

  何謂侵入性強的代碼,以上的代碼就是一個很好的例子:它直接將需要新增的業務代碼插入到原來的業務代碼中。這就帶來了一個問題,如果你是用這種方法頻繁地去擴展你的業務代碼,那么過不了多久,你就很難再理清你原來最基本的業務邏輯是什么樣的了,到最后會發現自己的代碼面目全非。

  所以,懶惰又聰明地小吳很快就想起了前不久在書上看到的代理模式,說時遲,那時快,小吳在鍵盤上“咔咔咔”一頓騷操作,以迅雷不及掩耳之勢敲完了代碼,哼,單身22年的手速可不是蓋的,你懂得,嘿嘿嘿。。。額。。跑題了。。。咱們回頭來看看小吳寫的代碼有多騷:

 1     //業務代理類
 2     public class BLLClassProxy : IBLLClass
 3     {
 4         private IBLLClass bllClass = new BLLClass();
 5 
 6         public void DoThing1()
 7         {
 8             //添加的業務邏輯
 9             LogUserOperation();
10 
11             //調用原始邏輯
12             bllClass.DoThing1();
13         }
14 
15         public void DoThing2()
16         {
17             LogUserOperation();
18 
19             bllClass.DoThing2();
20         }
21 
22         private void LogUserOperation()
23         {
24             //記錄每一個業務操作的時間和操作人信息
25         }
26     }

   如果你認真看過上面的代碼就會明白,BLLClassProxy這個類實現了BLLClass中所具備的所有業務函數,而且最終的業務邏輯都是來自對BLLClass類中業務函數的調用,BLLClassProxy類完全可以代替BLLClass類,而且在BLLClassProxy中還可以任意增加額外的業務邏輯,這很好的實現了我們需要的效果:對原始代碼沒有侵入性,且達到擴展業務邏輯的目的。我們把BLLClassProxy稱作為BLLClass的代理類,顧名思義,前者代理了后者所有的業務邏輯,故稱之為代理類。

  那么我們將會在實際的調用代碼中做如下改動:

1     IBLLClass bll = new BLLClass();      //==> 修改前:使用原始業務類
2                                  
3     IBLLClass bll = new BLLClassProxy(); //==> 修改后:使用代理業務類

  我們看到,由于原始業務類(BLLClass)與代理業務類(BLLClassProxy)都實現了接口IBLLClass,所以只需要將業務類聲明變量切換成代理類的實現就完成了整個需求的變動,無需做其他改動。當然,這還要得益于業務類的創建者的先見之明,提供了一個業務類的接口(IBLLClass),這也同時給我們提了個醒,盡量以接口的方式聲明業務類變量,就是所謂的“面向接口編程”。

  以上就是使用了代理模式來解決業務擴展的需求,其實再按細分的話,我們可以將以上的代理叫做 靜態代理。靜態代理指的就是在代碼編譯階段就已經生成代理類,而你需要在程序運行前就將代理類的代碼一五一十地寫好,如果有五十個代理類就寫五十個,有一百個就寫一百個。有人會問,啥?難不成還能有動態代理?當然,繼續看劇情發展......

懶人都愛用的“動態代理”

  項目經理大勇又一次笑瞇瞇地走向小吳,小吳不禁心頭一緊,心想,不妙。

  “小吳啊,客戶又提了幾個需求,不過不用慌,和上次要加的那個需求一樣,只不過這次把另外那20個業務類也加上同樣的業務邏輯。”

  小吳心中即便一萬個草泥馬,但是卻依然面帶微笑,點頭“好好好”。

  大勇也不忘夸小吳兩句:“上回加的那個代理類很不錯嘛,就照著那種方法加,很快的”。

  “很快?要不你來試試?”,小吳一邊嘴里嘀咕著,一邊在腦子里又蹦出了個新想法。

  又要手擼20多個代理類?有沒有什么別的方法能減少我的工作量呢,比如。。。自動生成代理類?

  年輕人就怕沒想法,有想法就要付諸于行動,于是小吳開始尋思著如何實現自己想法。

  自動生成代理類?從解決問題的思路出發有兩種解決方法:

  1、寫一個代碼生成器,生成代理類的代碼,然后再進行編譯。這種方法本質上還是我們上面介紹的靜態代理類,只不過不用我們手動編寫代理類的代碼,但是使用這種方法是極其痛苦的,雖然你不用手動編寫真正的代理類代碼,但是你必須去做一些代碼生成的配置吧,這個過程是及其繁瑣的,一個字概括,“太他媽LOW了!”

  2、在程序編譯好后跑起來的過程中,動態生成代理類。說的是什么?程序都已經跑起來了還能生成代理類?不錯,這就是動態生成代理類,借助的是c#語言的Emit高級特性。

  顯然我們需要的是動態生成代理類,那么這么說的話我們還必須要再學一學Emit創建類?小吳覺得照這樣下去這事兒得黃了,這已經不是臨陣磨槍了,這是要臨陣造輪子啊,這活還干不干了,等著造完這個輪子,黃花菜都涼了。

  于是當天晚上祖師爺就托夢給小吳,讓小吳用一用 Castle 框架,如獲至寶的小吳第二天就按照祖師爺的指示用上了Castle框架,嗯,真香~~

  我們在使用技術中大部分都是用既有的輪子,造輪子這種事情讓小吳干,小吳可不樂意。當然,造輪子對于我們的學習還是很有用處的,嘗試著去造造輪子,會提升你對技術更深層次的理解,因為你不再只是站在使用者的角度簡單地看問題。

  我們來看看Castle是怎么使用的:

  1、首先引用 Castle.Core 的dll文件

  2、創建建攔截器,攔截器的意思非常直觀,就是把運行著的方法攔下來,等會兒,你先別運行,先執行完我給你的方法A,再運行你自己的方法,然后再運行我給你的方法B......也就是說通過攔截器你可以在原始業務邏輯的前后或者任意可切入的點插入你想加入的業務邏輯。Castle 框架提供了一個標準的攔截器類 StandardInterceptor:

1     public class StandardInterceptor : MarshalByRefObject, IInterceptor
2     {
3         public StandardInterceptor();
4 
5         public void Intercept(IInvocation invocation);
6         protected virtual void PerformProceed(IInvocation invocation);
7         protected virtual void PostProceed(IInvocation invocation);
8         protected virtual void PreProceed(IInvocation invocation);
9     }

  你可以重寫 PreProceed、PerformProceed、PostProceed 三個虛方法,它們表示三個攔截點:執行前、執行中、執行后,你可以加入自己的業務邏輯到這三個攔截點里,實現業務的添加。

  于是像這樣建立一個攔截器類:

 1     public class LogInterceptor: StandardInterceptor
 2     {
 3         protected override void PostProceed(IInvocation invocation)
 4         {
 5             LogUserOperation();
 6         }
 7 
 8         private void LogUserOperation()
 9         {
10             //記錄每一個業務操作的時間和操作人信息
11             Console.WriteLine("記錄日志");
12         }
13     }

  然后這樣編寫創建業務類變量的代碼:

1     //代理生成類
2     ProxyGenerator proxyCreator = new ProxyGenerator();
3 
4     //通過Castle動態生成代理類
5     IBLLClass bll = proxyCreator.CreateInterfaceProxyWithTargetInterface<IBLLClass>(new BLLClass(), new LogInterceptor());

  我們可以看到最終業務類實例的創建是借助于Castle框架來生成的,其實Castle框架就替我們完成了動態代理類的生成,我們只需要在攔截器中寫好我們需要加入的攔截方法體代碼,然后注入到代理類。當然這里只是提供了使用Castle框架一個很簡單的例子,其實Castle中還有很多豐富的功能,有待各位看官根據自己的需求去發掘,師傅領進門,修行靠個人嘛,就是這么個道理。最后展示一下運行結果:

  

 

  動態代理可以稱的上是代理模式的最高境界,因為它借助動態生成的技術讓程序在運行過程中自動根據注冊條件生成代理類,省去了手寫靜態代理類的麻煩。同時動態代理模式也是AOP(面向切面編程)的一種很好的實現方式。


 

  

posted @ 2020-01-11 19:20  攻城的獅  閱讀(...)  評論(...編輯  收藏
ag二分彩