1. 程式人生 > >對代理模式理解(轉)

對代理模式理解(轉)

代理,代表打理,以他人的名義代表委託人打理其本職工作之外或不所能及的事務,達成合作關係並更高效地促成事務完成的目的。例如明星經紀人,他們並沒有像明星一樣會唱歌、跳舞或演戲,而是替明星打理一些無暇顧及的事務(這並不代表可以代理分外之事),比如推廣與宣傳,合同談判啊之類,達成和約後他們才會通知明星去表演。再比如機票銷售代理商既不造飛機也不提供乘機服務,他們只負責賣票,代理律師並不會因勝訴獲得賠償金或者敗訴受到法律制裁,他們只負責代理打官司,等等等等。

圖片來源網路,侵刪

 

生活中還有很多其他例項不勝列舉,但對於做技術的我們,以網路代理舉例相信是最合適不過了。

首先,我們上網前得去網路服務提供商(ISP)申請網際網路寬頻業務,於是順理成章光纖入戶,並拿到一個調變解調器,也就是我們俗稱的“貓”。好,“貓”實現了網際網路訪問介面,看程式碼。

作為調變解調器,一定有上網功能了,使用者的電腦只需要用網線連線這隻“貓”便接入網際網路了。就這麼簡單麼?然而某天我們發現孩子學習時總是偷偷上網看電影玩遊戲,於是我們決定對某些網站進行過濾,拒絕黃賭毒侵害未成年。那麼,我們需要在客戶終端電腦與貓之間加一層代理,用於過濾某些不良網站,最終我們決定購買一款有過濾功能的路由器。

注意看,在這裡路由器代理主要充當代理的角色,和之前的“貓”一樣,它同樣實現了網際網路介面,看似也是有上網功能的,其實不然。第12行程式碼對於網際網路訪問功能的實現一開始就做了個過濾,如果地址中帶有黑名單中的敏感字眼則禁止訪問並直接退出,反之則於第19行呼叫“貓”的網際網路訪問方法,看到了吧,最終還是呼叫“貓”的上網功能。注意此處為了對“貓”進行控制,代理專為此而生,我們直接於第7行例項化它而不是需要別人把它注入進來。好了,孩子現在來上網了,迫不及待執行之。

在第3行處,孩子例項化的不再是“貓”,而是被偷樑換柱的替換為路由器代理了,也就是說大家上網都連線路由器了,而不是直接去連“貓”,這樣不但省去了我們撥號的麻煩(路由器幫助撥號)而且孩子再也訪問不到亂七八糟的網站了。而這個代理自身其實並不具備訪問網際網路的能力,它只是簡單的呼叫“貓”上網功能,其存在目的只是為了控制對”貓“的互聯訪問,對其進行代理而已。

說到這裡大家有沒有發現這個代理模式是不是與裝飾器模式很類似?如果觀察UML類圖關係你會發現幾乎一模一樣,那這個模式存在的意義何在?其實,代理模式更強調的是對被代理物件的控制,而不是僅限於去裝飾目標物件並增強其原有的功能。就像明星的例子一樣,如果錢沒給夠,合同未達成,則不讓明星隨意作秀。

相信大家已經理解地很通透了吧,這也是我們最常用的代理模式了。其實還有一種叫動態代理,不同之處在於其例項化過程是在執行時完成的,也就是說我們不需要專門針對某個介面去寫這麼一個代理類,而是根據介面動態生成。

舉個例子,讓我們先忘掉之前的路由器代理,當我們內網中的上網裝置越來越多,路由器的Lan口已被佔滿不夠用了,於是我們決定換成交換機,看程式碼。

為了保持簡單,我們假設這個交換機Switch實現了局域網訪問介面Intranet,請注意這裡不是網際網路介面Internet。

這裡進行的是區域網檔案訪問,比如說是拷貝另一臺內網機器上的共享檔案,並且我們想保證與之前一樣的關鍵字過濾控制功能,也就是說不管是什麼地址都要先通過過濾,怎麼複用呢?

到這裡讓我們思考一下,貓實現的是網際網路訪問介面,交換機實現的是區域網訪問介面,那我們的過濾器代理類到底該怎麼寫?是實現Internet介面呢還是實現Intranet介面呢?要麼兩個都實現?再加進來新的類介面又要不停地改實現類嗎?這顯然行不通,過濾器無非就是一段過濾邏輯不必來回改動,這違反了設計模式開閉原則。動態代理應時而生,我們來看程式碼。

對於這個關鍵字過濾功能我們不再寫到代理類裡面了,而是另外寫個類並實現JDK反射包中提供的InvocationHandler介面,於第9行注入即將被代理的物件,不管是貓還是交換機什麼的它總歸是個Object,然後在第14行實現這個invoke呼叫方法,之後生成的動態代理將來會調進來跑這塊的邏輯,很顯然我們這裡依然保持不變的邏輯,在真實物件方法被執行之前運行了過濾邏輯加以控制。由於傳入的引數是被代理物件的方法method,以及一堆引數args,所以注意這裡第24行我們要用反射去呼叫被代理物件origin了,最後來看我們如何執行。

可以看到,我們不管是訪問網際網路還是區域網,只需要分別生成相應的代理並呼叫即可,相同的過濾器邏輯被執行了。如此一來,我們並不需要再寫任何的代理類了,只需要實現一次InvocationHandler就一勞永逸了,在執行時去動態地生成代理,達到相容任何介面的目的。

其實在很多框架中大量應用到了動態代理模式,比如Spring的面向切面AOP,我們只需要定義好一個切面類@Aspect,宣告其切入點@Pointcut(被代理的哪些物件的哪些方法,也就是這裡的貓和交換機的access以及accessFile),以及被切入的程式碼塊(要增加上去的邏輯,比如這裡的過濾功能程式碼,可分為前置執行@Before,後置執行@After,以及異常處理@AfterThrowing等),於是框架自動幫我們生成代理並切入目標執行。正如給每給方法前後加入日誌的例子,或者更經典的事務控制的例子,在所有業務程式碼之前先切入“事務開始”,執行過後再切入“事務提交”,如果拋異常被捕獲則執行“事務回滾”,如此就不必要在每個業務類中去寫這些重複程式碼了,一勞永逸,冗餘程式碼大量減少,開發效率驚人提升。

圖片來源網路,侵刪

 

兩耳不聞窗外事,一心只讀聖賢書,畢竟隔行如隔山,山外之事還是交給專家去代理吧。

轉自:

https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_10128955518875416376%22%7D&n_type=1&p_from=3