與介面相關的設計模式(2):代理模式、標識類型模式及常量介面模式

來源:互聯網
上載者:User

標籤:

在上文 與介面相關的設計模式(1) 中,詳細介紹了定製服務模式和適配器模式,下面我們來看第三種與介面相關的模式:代理模式。

代理模式

定義:為對象提供一種代理,以控制對這個對象的訪問。

分類:

  1. 遠程代理(Remote Proxy)—為不同地理的對象提供區域網路代表對象。(類似於用戶端和伺服器端)
  2. 虛擬代理(Virtual Proxy)—根據需要將資源消耗很大的對象進行延遲,真正需要的時候才進行建立。(網頁中圖片的載入,先用一張虛擬圖片進行顯示,等圖片載入完成後再進行顯示)
  3. 保護代理(Protect Proxy)—控制使用者的存取權限。(發帖功能)
  4. 智能引用代理(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動態代理實現步驟:

  1. 建立一個實現InvocationHandler的類,必須實現invoke方法
  2. 建立被代理的類和介面
  3. 調用Proxy的靜態方法,建立一個代理類
  4. 通過代理調用方法

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):代理模式、標識類型模式及常量介面模式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.