標籤:
在上文 與介面相關的設計模式(1) 中,詳細介紹了定製服務模式和適配器模式,下面我們來看第三種與介面相關的模式:代理模式。
代理模式
定義:為對象提供一種代理,以控制對這個對象的訪問。
分類:
- 遠程代理(Remote Proxy)—為不同地理的對象提供區域網路代表對象。(類似於用戶端和伺服器端)
- 虛擬代理(Virtual Proxy)—根據需要將資源消耗很大的對象進行延遲,真正需要的時候才進行建立。(網頁中圖片的載入,先用一張虛擬圖片進行顯示,等圖片載入完成後再進行顯示)
- 保護代理(Protect Proxy)—控制使用者的存取權限。(發帖功能)
- 智能引用代理(Smart Reference Proxy)—提供對目標對象一些額外的服務。(火車站代售處為火車站代理)
以下以智能引用代理為例,介紹代理的兩種實現方式:靜態代理和動態代理。
- 靜態代理:代理和被代理對象在代理之前是確定的,它們都實現了相同的介面或者繼承相同的抽象類別。
下面為簡單樣本,示範一個汽車對象被代理實現計時:
package com.proxy;public interface Moveable {//汽車的行駛功能 void move();}
如果沒有代理,汽車行駛的計時功能要在汽車本身行駛時實現:
package com.proxy;import java.util.Random;public class Car implements Moveable{ @Override public void move() { long startTime = System.currentTimeMillis(); System.out.println("汽車開始行駛"); try{ Thread.sleep(new Random().nextInt(1000)); System.out.println("行駛中"); }catch(InterruptedException e){ e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println("汽車行駛結束,行駛時間為:"+(endTime-startTime)+" ms"); }}
對於這種情景,我們可以建立一個代理來專門實現計時功能,如下:
package com.proxy;public class CarProxy1 extends Car{ @Override public void move() { long startTime = System.currentTimeMillis(); System.out.println("汽車開始行駛"); super.move(); long endTime = System.currentTimeMillis(); System.out.println("汽車行駛結束,行駛時間為:"+(endTime-startTime)+" ms"); }}
此時在Car類中的move方法可以只行駛,將計時功能交給代理去實現,Car類中的move方法代碼如下:
public void move() { try{ Thread.sleep(new Random().nextInt(1000)); System.out.println("行駛中"); }catch(InterruptedException e){ e.printStackTrace(); } }
現在我們要訪問汽車時不用再直接執行個體化Car類,而是通過訪問它的代理:CarProxy1執行個體,如下:
package com.proxy;public class Test { public static void main(String []args){ CarProxy1 car = new CarProxy1(); car.move(); }}
以上是通過繼承的方式實現,還可以通過組合的方式實現,即不通過繼承Car類,而是通過封裝Car類來調用Car執行個體的行駛功能。代碼如下:
package com.proxy;public class CarProxy2 implements Moveable{ private Car car; public CarProxy2(Car car) { super(); this.car = car; } @Override public void move() { long startTime = System.currentTimeMillis(); System.out.println("汽車開始行駛"); car.move(); long endTime = System.currentTimeMillis(); System.out.println("汽車行駛結束,行駛時間為:"+(endTime-startTime)+" ms"); }}
通過組合方式建立的代理類,在執行個體化代理前,需要先執行個體化Car對象,代碼如下:
package com.proxy;public class Test { public static void main(String []args){ Car car = new Car(); CarProxy2 carProxy2 = new CarProxy2(car); carProxy2.move(); }}
與適配器模式中的類的適配器模式、對象的適配器模式相似,代理模式中組合方式比繼承方式更靈活,推薦使用組合方式。比如再對Car實現日誌記錄等功能,使用繼承方式則要再建立一個代理繼承CarProxy1,之後再添加功能就要無限的向下繼承,難於維護。
動態代理
上例中對汽車建立了一個時間記錄代理,那麼如果我們要對火車、飛機都記錄時間呢?就需要分別對火車、飛機建立代理類。我們需要一個方法來把記錄時間這個代理抽離出來,這樣當需要對不同的交通工具實現記錄時間功能的時候,直接運用這個抽離出的代理,這樣可大大減少代碼的重用率。這就引出了動態代理。
動態代理就是動態產生代理,實現對不同類,不同方法的代理。下面來看JDK動態代理。以下為JDK動態代理的實現機制:
,JDK的動態代理其實就是在代理類ProxySubject與被代理類RealSubject之間加入了一個ProxyHandler類,這個ProxyHandler類實現了InvocationHandler介面,它充當交易處理器,比如類似於上面給汽車計時的Tlog,時間處理等事務都是在這個ProxyHandler類中完成的。
InvocationHandler介面源碼如下:
package java.lang.reflect;public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}
其中第一個參數proxy代表代理對象;第二個參數method代表被代理的方法;第三個參數代表該方法的參數數組。
JDK動態代理實現步驟:
- 建立一個實現InvocationHandler的類,必須實現invoke方法
- 建立被代理的類和介面
- 調用Proxy的靜態方法,建立一個代理類
- 通過代理調用方法
1,建立一個實現InvocationHandler的類,必須實現invoke方法:
package com.jdkproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class TimeHandler implements InvocationHandler { private Object object; public TimeHandler(Object object) { super(); //被代理的對象 this.object = object; } /** * proxy: 代理對象 * method: 被代理對象的方法 * args:方法的參數 * return: 調用方法的傳回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); System.out.println("汽車開始行駛"); method.invoke(object); long endTime = System.currentTimeMillis(); System.out.println("汽車行駛結束,行駛時間為:"+(endTime-startTime)+" ms"); return null; }}
步驟 2,3,4,以下為Test類:
package com.jdkproxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import com.proxy.Car;import com.proxy.Moveable;public class Test { public static void main(String[]args){ Car car = new Car(); InvocationHandler h = new TimeHandler(car); Class<?> cls = car.getClass(); /** * loader:類載入器 * interfaces:實現的介面 * h InvocationHandler */ Moveable m = (Moveable) Proxy.newProxyInstance( cls.getClassLoader(), cls.getInterfaces(), h); m.move(); }}
運行結果:
CGLIB動態代理:
CGLIB代理實現原理類似於JDK動態代理,只是它在運行期間產生的代理對象是針對目標類擴充的子類。CGLIB是高效的代碼產生包,底層是依靠ASM(開源的java位元組碼編輯類庫)操作位元組碼實現的,效能比JDK強。主要使用於改造遺留系統,這些系統一般不會繼承特地的介面。
cglib動態代理實現需要匯入相關包,此處匯入的為cglib-nodep-2.1_3.jar,然後需要建立交易處理器類,並實現MethodInterceptor介面,交易處理器類CglibProxy源碼如下:
package com.cjlibproxy;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //設定建立子類的類 enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } /** * 攔截所有目標類方法的調用 * obj 目標類的執行個體 * m 目標方法的反射對象 * args 方法的參數 * proxy 代理類的執行個體 */ @Override public Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable { //代理的商務邏輯 System.out.println("代理:開車"); //代理類調用父類的方法 proxy.invokeSuper(obj, args); //代理的商務邏輯 System.out.println("代理:到了"); return null; }}
有一個火車類,並沒有實現介面:
package com.cjlibproxy;public class Train { public void move(){ System.out.println("火車行駛中..."); }}
測試類別:
package com.cjlibproxy;public class Test { public static void main(String[]args){ CglibProxy proxy = new CglibProxy(); Train t = (Train) proxy.getProxy(Train.class); t.move(); }}
測試結果:
總結: JDK動態代理和CGLIB動態代理的使用都很簡單,如果能理解內部的具體實現流程才能更深刻的理解動態代理,關於JDK動態代理的類比實現會在之後文章補充。
標識類型模式
定義一個不包含任何方法的介面,用它僅僅來表示一種抽象類別型。所有實現該介面的類意味著屬於這種類型。
比如定義一個Food介面,其中不包含任何方法:
public interface Food{}//實現該介面的類都是食物類型
魚肉:
public class Fish implements Food{...}
進食方法:
public void eat(Food food){...}
進食:
Food fish = new Fish();//Fish實現了Food介面,標識其食物類型eat(fish);//合法Book book = new Book();//Book未實現Food介面eat(book);//編譯錯誤
所謂標識類型模式就是藉助Java編譯器來對傳給eat()方法的food參數進行語義上的約束。Food介面被稱為標識類型介面,這種介面沒有任何方法,僅代表一種抽象類別型。在JDK中,有如下兩個典型的表示類型介面。
- java.io.Serializable介面:實現該介面的類的執行個體可以被序列化
- java.io.Remote介面:實現該介面的類的執行個體可以作為遠程對象
常量介面模式
在一個軟體系統中會使用一些常量,一種流行的做法是把相關的常量放在一個專門的常量介面中定義,例如:
package com.FinalInterface;public interface MyConstants { public static final double MATH_PI = 3.1415926; public static final double MATH_E = 2.71828;}
以下Circle類需要訪問以上MATH_PI常量,一種方式是採用直接存取方式,如下:
package com.FinalInterface;public class Circle { private double r;//半徑 public Circle(double r){ this.r = r; } public double getCircumference(){ return 2 * r * MyConstants.MATH_PI; }}
在JDK1.5中引入了”import static“語句,它允許類A直接存取另一個介面B或類B中的靜態常量,而不必指定介面B或類B的名字,而且類A無須實現介面B或者繼承類B。如下:
package com.FinalInterface;import static com.FinalInterface.MyConstants.*;public class Circle { private double r;//半徑 public Circle(double r){ this.r = r; } public double getCircumference(){ return 2 * r * MATH_PI; }}
import static 語句既可以簡化編程,又能防止Circle類繼承並公開MyConstants中的靜態常量。
結合上我的上一篇:與介面相關的設計模式(1):定製服務模式和適配器模式詳解,一共記錄了與介面相關的5種設計模式,分別是定製服務模式、適配器模式、代理模式、標識類型模式以及常量介面模式。介面是構建松耦合的軟體系統的重要法寶。介面的優勢在於一個類可以實現多個介面,介面獲得這一優勢是以不允許為任何方法提供實現作為代價的(暫不考慮JAVA8的Default方法)。
我們可以把介面作為系統中最高層次的抽象類別型。站在外界使用者(另一個系統)的角度,介面向使用者承諾系統能提供哪些服務;站在系統本身的角度,介面指定系統必須實現哪些服務。系統之間通過介面進行互動,這可以提高系統之間的松耦合。
至於抽象類別呢,它用來定製系統中的擴充點。可以把抽象類別看作介於”抽象“和”實現“之間的半成品。抽象類別力所能及的完成了部分實現,但還有一些功能有待於它的子類去實現。
與介面相關的設計模式(2):代理模式、標識類型模式及常量介面模式