Head First Pattern之代理模式

          Posted on 2008-09-13 23:48 Lv Yuanfang 閱讀(620) 評論(0)  編輯  收藏

          Head First Pattern之代理模式


          去年買的Head First Pattern英文版,看了一點點,看起來還是比較吃力。。今年開始一點點的看,慢慢的看進去了,真是好書啊,一點點的從實際例子入手,一步步的、循序漸進的說明每一個設計模式,真是足夠的深入淺出!以前也看過閻宏的《Java與模式》,結合中國的傳統道家文化、儒家思想,甚至西游記、紅樓夢、女媧造人都用上了,說的是也算夠透徹了的,但是總感覺還是有些東西理解的不太深。
          下面總結下這些天看的代理模式。。

          一句話概括代理模式,就是用代理對象對真實對象的訪問控制,代理對象和真實對象都實現同一個Subject接口。
          類圖表示如下:
          圖截自http://refcardz.dzone.com/里的免費書:Design Patterns
          個人理解,代理模式在現實例子里,可以有非常多的變種,關鍵在于代理對象如何實現對真實對象的訪問控制。變化在于訪問控制的方式。著重說明下書中的3個例子,就是3種代理模式的使用場合。。

          遠程代理

          遠程代理的例子是java中的RMI。真是足夠深入淺出的,讓我以前對RMI非常模糊的印象也漸漸清晰起來。咱們一步步細細道來。。

          第一步:定義遠程接口

          1.繼承java.rmi.Remote接口
          定義服務接口,服務接口必須繼承自Remote接口。Remote接口是一個標記接口,就是這個接口,沒有任何要實現的方法,僅僅是用來標識其實現類具有某種功能(個人理解),就像Serializable接口,僅僅表示實現這個接口的類能被序列化。
          public interface MyRemote extends Remote {

          2.服務接口中所有方法拋出RemoteException異常
          RMI客戶端的方法調用其實是調用實現Remote接口的Stub(樁),樁的實現是基于網絡和IO的(底層就是socket),客戶端在調用方法過程中,任何錯誤都有可能發生,所以必須讓客戶端知道所發生的異常,并能捕捉。
          import java.rmi.*;
          public interface MyRemote extends Remote {
          ?? ?public String sayHello() throws RemoteException;
          }

          3.保證返回值和參數必須是可序列化的
          遠程方法的參數要通過網絡傳輸,因此必須是可序列化的,返回值也是同樣。如果用原生類型(int、float等)、String、集合等,就沒問題,如果用自己的類型,必須實現Serializable接口(和Remote接口一樣,都是標記接口)。

          第二步:實現遠程服務

          1.實現遠程接口
          public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote{
          ?? ?public String sayHello(){
          ?? ??? ?return "Hello, I'm server.";
          ?? ?}
          }

          2.繼承UnicastRemoteObject?
          要想成為一個遠程服務對象,需要有遠程的功能。最簡單的方法就是實現UnicastRemoteObject方法了。

          3.聲明一個無參數的構造函數,且拋出RemoteException
          public MyRemoteImpl() throws RemoteException{}

          4.用RMI registry注冊服務
          實現遠程服務后,要布遠程服務供客戶端使用。要實例化一個遠程服務,放入RMI注冊表中。注冊了服務實現對象后,RMI會把Stub(樁)放入注冊表,讓客戶端使用。
          try{
          ?? ?MyRemote service = new MyRemoteImpl();
          ?? ?Naming.rebind("RemoteHello",service);
          }catch(Exception e){
          ?? ?// ...
          }

          第三步:生成Stub和Skeletons(樁和骨架)

          1.在遠程實現類上運行rmic(不是遠程接口)
          rmic MyRemoteImpl(類名,不帶.class)
          會生成樁和骨架代碼:MyRemoteImpl_Stub.class、MyRemoteImpl_Skel.class
          rmic是jdk bin目錄下的工具

          第四步:運行rmiregistry

          1.rmiregistry
          必須讓rmiregistry能訪問到你的服務相關類,要么把類放入classpath,要么在classes目錄下直接運行rmiregistry

          第五步:啟動服務

          1.另一個dos窗口里啟動服務類
          java MyRemoteImpl


          客戶端調用方法:
          MyRemote service = (MyRemote)Naming.lookup("rmi://127.0.0.1/RemoteHello");
          String msg = service.sayHello();// 調用樁的方法

          通過RMI registry查找服務后,返回樁,客戶端必須用MyRemoteImpl_Stub.class和MyRemote.class。樁MyRemoteImpl_Stub.class、骨架MyRemoteImpl_Skel.class、MyRemote.class、MyRemoteImpl.class必須在服務端。


          這么多亂七八糟的跟代理模式有什么關系?
          其實客戶端返回的MyRemote,其實是MyRemote_Stub,就是代理對象了,服務端的MyRemoteImpl及時實際對象,通過RMI,來獲得遠程對象的代理,再通過代理,來訪問實際對象(遠程服務實現類MyRemoteImpl)所實現的遠程服務方法(MyRemote定義)。對應類圖,每個類在代理模式中的角色分別是:
          Subject:MyRemote接口
          RealObject:MyRemoteImpl服務實現類
          Proxy:MyRemote_Stub樁

          在RMI中,找到服務后,拿到的MyRemote service其實是一個代理對象(MyRemote_Stub),對代理對象的方法調用,實際是通過RMI來訪問遠程服務實現對象的方法。也就是說代理對象MyRemote service(實際是MyRemote_Stub)通過RMI機制對遠程服務對象來做訪問控制,也就實現了代理模式。

          虛擬代理

          虛擬代理舉的是一個Swing的例子。

          我是這么理解的:一個對象的創建非常耗時,通過代理對象去調用,在真實對象創建前,返回一個假的調用,等真實對象創建好了,這時候返回給客戶端的就是一個真實對象的相應方法調用。


          也就是延遲加載的問題,Swing例子中,要顯示一個Icon,但是要通過網絡加載一個圖片,在圖片通過網絡加載成功前,先顯示一個“加載中,請稍候...”(如果是真實對象的調用,應該顯示一個圖片),在代理對象中通過后臺線程去加載圖片,加載完了后,再偷偷的把“加載中,請稍候...”的字樣偷偷換成加載成功后的圖片。


          沒想到這也算代理模式的一種應用場景。以前有這么在Swing中用過,需要從數據庫中查找數據,但是比較耗時,就先顯示“加載數據中,請稍候...”,等加載完了,再在JTable中顯示出來。如果用代理模式的方式來思考,好像比較的好吧。。


          同樣在jsp頁面里,通過ajax來加載數據好像也是這樣的道理,數據沒加載之前就是“加載中...”,加載完了再通過innerHTML來改變顯示,也是同樣的延遲加載問題。

          如果用代理模式的方式來考慮,可以定義一個JavaScript類(這個類其實是個代理),這個類有個方法要顯示一些從Server取出的數據,但是調用顯示方法時,后臺數據還沒有加載,就先顯示加載中請稍候之類的文本,這時候通過ajax從Server取數據(創建真實對象),取出來之后在回調函數中更新顯示HTML元素的innerHTML。跟那個Swing的例子一模一樣吧。不過好像JavaScript中好像沒有誰會定義接口、實現、代理對象吧,但是思路其實是一樣的。

          不知道這樣理解代理模式,算不算曲解。。。


          JDK動態代理

          jdk里的動態代理支持,主要是通過java.lang.reflect包中Proxy、InvocationHandler等幾個類來實現的。具體如何實現可參考JDK中文文檔。

          使用場合:

          好像在在一本Hibernate的書上,對數據庫Connection的close方法調用,用動態代理的方式來攔截,并不真正關閉連接,而是返回到數據庫連接池中。

          在Spring中的攔截貌似有些是用動態代理實現的?不過動態代理使用時要基于接口,但是Spring是使用動態生成字節碼的方式?對Spring內部實現機制不熟。。不敢妄自猜測。。等有時間好好研究再來說明。。

          動態代理,我覺得最好的使用場合是給方法調用增加預處理和后處理,更加靈活了,可以做一些額外的事,同時也做到無侵入的解耦合,因為代理對象和實際對象的接口是一樣的,唯一需要注意的地方是,客戶端調用者是拿的接口,接口到底是使用代理對象還是實際對象,調用者并不知道,這就需要對代理對象的創建用類似工廠的方式來封裝創建。比如一下代碼:
          PersonBean getOwnerProxy(PersonBean person){
          ?? ?return (PersonBean)Proxy.newProxyInstance(
          ?? ??? ? ? ? person.getClass().getClassLoader(),
          ?? ??? ??? ? person.getClass().getInterfaces(),
          ?? ??? ??? ? new OwnerInvocationHandler(person));
          }

          PersonBean為Subject接口,OwnerInvocationHandler實現InvocationHandler接口。

          和Decorator的比較

          Decorator模式在jdk的java.io包中使用非常廣泛。主要用來為一個類添加新的行為。
          而Proxy模式中,代理對象并不對實際對象添加新的行為,只是對實際對象做訪問控制。


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           

          posts - 11, comments - 2, trackbacks - 0, articles - 0

          Copyright © Lv Yuanfang

          主站蜘蛛池模板: 加查县| 长岭县| 仙游县| 文登市| 盘锦市| 绥江县| 鸡东县| 芦山县| 兴海县| 龙口市| 敦煌市| 公主岭市| 余江县| 治县。| 綦江县| 革吉县| 合川市| 胶南市| 成都市| 华亭县| 江安县| 忻城县| 兴业县| 方城县| 逊克县| 金昌市| 恩施市| 涡阳县| 澳门| 故城县| 赣榆县| 类乌齐县| 潍坊市| 临桂县| 大港区| 柳州市| 年辖:市辖区| 醴陵市| 泸定县| 琼结县| 元阳县|