設計模式詳解之代理模式

代理模式是咱們使用率比較高的一個模式。它的定義是爲其餘對象提供一種代理以控制對這個對象的訪問。java

若是隻是從定義上來看,可能沒法理解。爲何要用代理來對這個對象來訪問,我直接訪問不行嗎?行,固然行了。可是咱們使用代理天然是有代理的優點,咱們舉個簡單例子來講明一下。編程

有一個房東,他有一座房子要出售。可是房子買賣呢,咱們知道,須要讓買房的人來看房,有意向以後還須要和顧客簽定一系列的合同,實在複雜。咱們的房東比較懶,他不想作那麼多事情,他只想單純賣房子。因而他找了一箇中介,由中介來代替房東作這些事情。這個中介就能夠說是咱們的代理。若是以爲仍是以爲抽象的話,下面咱們會用實際開發代碼來演示代理模式,深刻理解。設計模式

代理模式的三個重要角色:ide

Subject抽象主題角色this

  • 抽象主題類能夠是抽象類也能夠是接口,是一個最普通的業務類型定義,無特殊要求。

RealSubject具體主題角色編碼

  • 被委託角色,被代理角色。也就是咱們的房東

Proxy代理主題角色.net

  • 也叫作委託類、代理類。它負責對真實角色的應用,把全部抽象主題類定義的方法限制委託給真實主題角色實現,而且在真實主題角色處理完畢先後作預處理和藹後處理工做。(咱們的中介)

關於咱們的代理模式大體能夠分紅兩種,靜態代理模式和動態代理模式。下面咱們會用代碼來分別演示一下兩種代理的區別。設計

1 靜態代理

靜態代理呢,就是咱們的每個具體主題角色都有本身專門的代理角色。咱們用代碼來講吧,咱們開發業務的時候,須要抽象service接口和具體實現。咱們模擬一個增刪改查。代理

package proxydemo;

public interface Service {
    
    //好比咱們的業務有這四個方法
    
    void add();
    void delete();
    void update();
    void query();
    
}

而後就是咱們去實現這個接口日誌

package proxydemo;

public class ServiceImpl implements Service {
    @Override
    public void add() {
        System.out.println("增長操做");
    }

    @Override
    public void delete() {
        System.out.println("刪除操做");
    }

    @Override
    public void update() {
        System.out.println("更新操做");
    }

    @Override
    public void query() {
        System.out.println("查詢操做");
    }
}

這個service接口就能夠看做是咱們的抽象主題角色,具體實現類就是咱們的具體主題角色。咱們能夠在客戶端使用

package proxydemo;

public class Client {
    public static void main(String[] args) {
        ServiceImpl service = new ServiceImpl();
        service.add();
        service.delete();
        service.update();
        service.query();
    }
}

|----------控制檯輸出--------|
增長操做
刪除操做
更新操做
查詢操做

Process finished with exit code 0

可是若是這個時候來了一個需求,說要在咱們使用方法後,日誌可以記錄下來是進行了什麼操做。那咱們容易想到的就是在咱們的實現類去修改,在使用後方法後用日誌記錄下來。可是咱們在開發中最忌諱就是修改原有的代碼,由於咱們是要符合開閉原則的。全部咱們就能夠用代理模式來

package proxydemo;

//咱們的代理角色,在不修改原來的代碼狀況下,擴展了日誌的功能
public class ProxyServiceImpl implements Service{

    //使用組合模式代替繼承
    private ServiceImpl service;

    public void setService(ServiceImpl service) {
        this.service = service;
    }

    @Override
    public void add() {
        System.out.println("日誌輸出:使用add方法");
        service.add();
    }

    @Override
    public void delete() {
        System.out.println("日誌輸出:使用delete方法");
        service.delete();
    }

    @Override
    public void update() {
        System.out.println("日誌輸出:使用update方法");
        service.update();
    }

    @Override
    public void query() {
        System.out.println("日誌輸出:使用query方法");
        service.query();
    }
}

而後在咱們的客戶端使用的時候,只須要給代理角色設置須要被代理的角色就行

package proxydemo;

public class Client {
    public static void main(String[] args) {
        //實例化咱們原來的功能
        ServiceImpl service = new ServiceImpl();
        //實例咱們的代理角色
        ProxyServiceImpl proxyService = new ProxyServiceImpl();
        //設置須要被代理的角色
        proxyService.setService(service);
        //使用有日誌功能的代理方法
        proxyService.add();
        proxyService.delete();
        proxyService.update();
        proxyService.query();
    }
}



|----------控制檯輸出--------|
日誌輸出:使用add方法
增長操做
日誌輸出:使用delete方法
刪除操做
日誌輸出:使用update方法
更新操做
日誌輸出:使用query方法
查詢操做

Process finished with exit code 0

1.1 靜態代理總結

好處:

  • 可使真實角色的操做更加純粹!不用去關注一些公共的業務
  • 一些非核心的功能交給了代理角色,實現了業務的分工
  • 公共業務擴展的時候,方便集中管理

缺點:

  • 上面也說了,每個真實角色就會產生一個代理角色,代碼量翻倍

2 動態代理

動態代理就是爲了解決咱們上面那個代碼量大的解決問題,也是咱們真正在開發中會去使用的代理模式。咱們的動態代理有兩種方式去實現,一個是JDK動態代理(面向接口),一個是CGlib動態代理(面向類)。咱們這裏主要是介紹概念,因此就用JDK動態代理來實現咱們上面的例子,另外一種的使用能夠看這裏(動態代理的兩種方式以及區別

(補充:如今也出了Javassit去實現了)

關於JDK的動態代理,咱們在java.lang.reflect包下,有這麼一個接口

Interface InvocationHandler

InvocationHandler是由代理實例的調用處理程序實現的接口

每一個代理實例都有一個關聯的調用處理程序。 當在代理實例上調用方法時,方法調用將被編碼並分派到其調用處理程序的invoke方法。

還有一個Proxy類,它提供了建立動態代理類和實例的靜態方法,它也是由這些方法建立的全部動態代理類的超類。

能夠經過反射來建立代理

Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                          new Class<?>[] { Foo.class },
                                          handler);

話很少說,咱們用代碼了實現一下更容易明白這些抽象的解釋。

例子仍是調用咱們的上面的例子,只不過這個時候,咱們的代理類不須要去實現了,咱們實現InvocationHandler接口寫調用程序就好了。

package proxydemo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    public Object target;

    public void setTarget(Object object) {
        this.target = object;
    }


    //經過反射生成獲得代理類
    public Object getProxy(){
        //三個參數,當前的類加載器,被代理的接口,以及一個InvocationHandler,當前便可
        return Proxy.newProxyInstance
                (this.getClass().getClassLoader(),
                        target.getClass().getInterfaces(),this);
    }

    //處理代理實例,返回結果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //經過反射來調用方法
        Object result = method.invoke(target,args);
        return result;
    }
}

咱們在咱們的客戶端使用的時候,須要去實例化一個InvocationHandler就好了。

package proxydemo;

public class Client {
    public static void main(String[] args) {
        //實例一個咱們的須要被代理的角色
        ServiceImpl service = new ServiceImpl();
        //實例咱們的代理調用處理
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //設計被代理的角色
        pih.setTarget(service);
        //經過反射了來實例咱們的代理類
        Service proxyService = (Service) pih.getProxy();
        proxyService.add();
        proxyService.delete();
        proxyService.update();
        proxyService.query();
    }
}

|----------控制檯輸出--------|
增長操做
刪除操做
更新操做
查詢操做

Process finished with exit code 0

具體的調用過程就是

這個時候若是咱們要增長一個日誌輸出功能的話,只須要在咱們的ProxyInvocationHandler裏面增長就行。

package proxydemo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Object target;

    //設計被代理的接口
    public void setTarget(Object object) {
        this.target = object;
    }

    //經過反射生成獲得代理類
    public Object getProxy(){
        //三個參數,當前的類加載器,被代理的接口,以及一個InvocationHandler,當前便可
        return Proxy.newProxyInstance
                (this.getClass().getClassLoader(),
                        target.getClass().getInterfaces(),this);
    }

    //處理代理實例,返回結果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //經過反射來調用方法
        log(method.getName());  //將方法名傳給咱們的輸出方法
        Object result = method.invoke(target,args);
        return result;
    }
    
    //新增長的日誌輸出功能
    public void log(String msg){
        System.out.println("日誌輸出,調用了"+msg+"方法");
    }
}

客戶端輸出:

日誌輸出,調用了add方法
增長操做
日誌輸出,調用了delete方法
刪除操做
日誌輸出,調用了update方法
更新操做
日誌輸出,調用了query方法
查詢操做

Process finished with exit code 0

看到這裏可能仍是會有小夥伴不是很懂, 上面咱們的靜態代理實現了一個代理類,這裏不也是實現了一個調用處理嗎?注意,這裏咱們的調用處理但是一個能夠複用的,只要你是實現了基於接口實現的業務就能夠調用。好比咱們一個龐大的業務的層,有不少的servcie接口,均可以統一使用這個動態代理模板。咱們不須要在實現階段去關心代理誰,在使用的時候才指定代理誰!!!

3 總結

關於代理咱們也瞭解,它主要的優勢也很明顯

  • 職責清晰。真實的角色就是實現實際的業務邏輯,不用關心其餘非本職責的事務,經過後期的代理完成一件事務,附帶的結果就是編程簡潔清晰。
  • 高擴展性。具體主題角色是隨時都會發生變化的,只要它實現了接口,甭管它如何變化,都逃不脫如來佛的手掌(接口),那咱們的代理類徹底就能夠在不作任何修改的狀況下使用。
  • 智能化。都不用本身實現代理,只須要在使用的時候去指定就好了。

3.1 使用場景

關於代理模式應用的場景,一個比較典型的動態代理就是Spring AOP了,咱們用AOP來面向切面編程。在咱們完成核心業務以後,而後能夠橫向擴展咱們的周邊業務,好比日誌輸出,監控等。具體的能夠去了解AOP,瞭解以後對其餘的場景,下次一見到就能看出來這用到了代理模式了。

4 參考資料

碰見狂神說:通俗易懂的23中設計模式教學(視頻)

設計模式之禪(第二版)

相關文章
相關標籤/搜索