1. 程式人生 > 實用技巧 >【設計模式(12)】結構型模式之代理模式

【設計模式(12)】結構型模式之代理模式

個人學習筆記分享,當前能力有限,請勿貶低,菜鳥互學,大佬繞道

如有勘誤,歡迎指出和討論,本文後期也會進行修正和補充


前言

“中介”這個職業大家都知道,比如買房租房,我們一般是與中介進行交流、看房、議價等等,甚至我上一次租的房住了一年都沒見過房東,同理房東也沒見過我。。。

還有譬如買火車票並不需要去火車站,在攜程或者12306app上就可以購買;訂酒店通過美團或者攜程也能夠完成,並不需要本人去酒店;還有瀏覽那些“不存在”的網站的時候,我們需要“梯子“等等


以租房中介來說,一方面他是我的中介,代替我去跟房東談價格談房屋配置,談妥之前房東並不知道我是誰,相當於我的代理。

另一方面他也是房東的中介,代替房東跟我談價格談合同,談妥之前房東同樣不知道我是誰,相當於房東的代理。

而且在代理的過程中,並不一定會原話轉達給對方,大概率會做一些小的變動,以促成合同的完成。這是他的能力範圍內的事情,當然會怎麼做看個人咯。


說到底,之所以需要中介,是因為我們不想或者不能直接訪問目標物件


代理模式的定義:由於某些原因需要給某物件提供一個代理以控制對該物件的訪問。

這時,訪問物件不適合或者不能直接引用目標物件,代理物件作為訪問物件和目標物件之間的中介。


1.介紹

使用目的:為其他物件提供一種代理以控制對這個物件的訪問

使用時機:需要在訪問一個類時新增一些控制

解決問題:由於某些原因客戶端不能或者不想要直接訪問目標物件

實現方法:新增中間層,用於實現與被代理類組合。客戶端訪問中間層,以間接訪問被代理類

應用例項

  • Windows系統的快捷方式
  • 機票、車票、酒店等代售點
  • 房屋中介、職務中介等
  • spring中的aop(面向切面程式設計)

優點

  • 客戶端不能直接訪問目標物件,可以保護目標物件,提高安全性
  • 客戶端與目標物件解耦,職責清晰,也提高了程式的擴充套件性
  • 代理可以擴充套件目標物件的功能

缺點

  • 代理模式需要在系統中新增類,提高了系統體積
  • 客戶端和目標物件中增加了代理物件,會一定程度上降低執行速度
  • 實現代理模式需要額外的工作,有些代理模式的實現非常複雜

使用場景:按職責來劃分,通常有以下使用場景

說來話長,不多贅述

  • 遠端代理
  • 虛擬代理
  • Copy-on-Write 代理
  • 保護(Protect or Access)代理
  • Cache代理
  • 防火牆(Firewall)代理
  • 同步化(Synchronization)代理
  • 智慧引用(Smart Reference)代理

2.結構

代理模式的主要角色如下

  • 抽象主題(Subject)類:通過介面或抽象類宣告真實主題和代理物件實現的業務方法。

  • 真實主題(Real Subject)類:實現了抽象主題中的具體業務,是代理物件所代表的真實物件,是最終要引用的物件。

  • 代理(Proxy)類:提供了與真實主題相同的介面,其內部含有對真實主題的引用,它可以訪問、控制或擴充套件真實主題的功能。

  • 代理類和真實主題類都需要實現介面抽象主題類,以保證其對外介面一致

  • 客戶端呼叫代理類中的介面,實際上是對真實主題類中方法加上前置後置處理後的方法,但仍然需要呼叫真實主題類的方法

  • 代理類需要持有一個真實主題類物件,以呼叫其中的目標方法


3.實現步驟

  1. 定義抽象主題介面

    interface Subject {
        void Request();
    }
    
  2. 定義真實主體類,實現抽象主題介面

    class RealSubject implements Subject {
        @Override
        public void Request() {
            System.out.println("訪問真實主題方法...");
        }
    }
    
  3. 定義代理類,實現抽象主題介面,並持有一個真實主題物件

    class Proxy implements Subject {
        private RealSubject realSubject;
    
        public Proxy() {
            this.realSubject = new RealSubject();
        }
    
        @Override
        public void Request() {
            preRequest();
            realSubject.Request();
            postRequest();
        }
    
        private void preRequest() {
            System.out.println("訪問真實主題之前的預處理。");
        }
    
        private void postRequest() {
            System.out.println("訪問真實主題之後的後續處理。");
        }
    }
    

    需要持有一個真實主題物件,構造這個物件的時間可以按照業務處理,一般有下面3種情況

    • 寫在代理類建構函式中,構造代理類的同時構造真實主題物件
    • 寫在介面中,呼叫介面時檢查是否已構造,沒有則此時構造
    • 在客戶端中構造真實主題物件,通過代理類的建構函式傳入代理類

完整程式碼

package com.company.test.proxy;

//抽象主題
interface Subject {
    void Request();
}

//真實主題
class RealSubject implements Subject {
    @Override
    public void Request() {
        System.out.println("訪問真實主題方法...");
    }
}

//代理
class Proxy implements Subject {
    private RealSubject realSubject;

    public Proxy() {
        this.realSubject = new RealSubject();
    }

    @Override
    public void Request() {
        preRequest();
        realSubject.Request();
        postRequest();
    }

    private void preRequest() {
        System.out.println("訪問真實主題之前的預處理。");
    }

    private void postRequest() {
        System.out.println("訪問真實主題之後的後續處理。");
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.Request();
    }
}

執行結果

4.正向代理與反向代理

都是代理,一個正一個反,那麼到底是是啥反了?答案是代理的物件反了

通常說三個角色,客戶端—代理—服務端

  • 正向代理是代理客戶端,隱藏客戶端的真實身份

    如VPN,VPN伺服器會代替我們去訪問目標網站。對於目標網站而言,互動的物件是代理,他並不知道代理的背後是誰,甚至不知道是不是代理。

    目的正是讓服務端不知道是誰在訪問。

  • 反向代理是代理服務端,隱藏服務端的真實身份

    如10086或者10010客服熱線,我們撥的同一個電話,但是對面的人大概率每次都不一樣,但他們都會說自己是客服,實際上我們也不關心是誰,我們只關心能不能解決自己的問題。

    很顯然客服不止一個,我們撥過去後系統會選擇空閒的客服接線給我們,以保證不會出現長時間排隊的情況。

    雖然代理了服務端,隱藏其身份,但這個不是目的。

    目的是讓代理去選擇合適的服務端,只是沒打算告知客戶端罷了。對客戶端而言,互動物件是代理,並不知道代理的物件是誰

正向代理代理的物件是客戶端,反向代理代理的物件是服務端

主要是用在伺服器層面的,為了避免偏題,此處不再贅述,有興趣的可以去這兩個地方看看,講得不錯

https://blog.csdn.net/lixiangss1993/article/details/87934562

https://www.zhihu.com/question/24723688


5.靜態代理與動態代理

上述步驟就是靜態代理,但存在以下缺陷

代理主題是為真實主題專門配備的,因此是一對一的關係,且使用同一個介面進行規範。

因此代理主題必須基於已有的真實主題,每新增一個真實主題需要新增一個代理主題。


動態代理的出現則是為了解決這種缺陷

動態代理利用反射機制在執行時建立代理類,客戶端呼叫代理來執行服務端方法,因此一個代理類適用於所有服務端。

package com.company.test.invocation;

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

//抽象主題
interface Subject {
    void Request(String s);
}

//真實主題
class RealSubject implements Subject {
    @Override
    public void Request(String s) {
        System.out.println("訪問真實主題方法...");
    }
}

//代理
class ProxyHandler implements InvocationHandler {
    private Object object;

    public ProxyHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke " + method.getName());
        method.invoke(object, args);
        System.out.println("After invoke " + method.getName());
        return null;
    }
}

public class InvocationTest {
    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject realSubject = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyHandler(subject));
        realSubject.Request("hello?");
    }
}

通過這樣的處理方式,我們將subject託管給ProxyHandler代理。

我們可以在invoke方法中進行前置處理,後置處理,甚至修改引數(args),或者直接丟擲異常

實際使用的過程中,我們通常使用AOP(面向切面程式設計)以註解的方式進行代理,有興趣的可以看看之前我整理的JWT的自定義切面攔截器

https://blog.csdn.net/qq_25978501/article/details/108409031


後記

代理模式真要學完可能得幾天時間,有空再整理細說吧,先做了解,實戰中會遇到不少的,特別是AOP幾乎算是面試必問的了


作者:Echo_Ye

WX:Echo_YeZ

Email :[email protected]

個人站點:在搭了在搭了。。。(右鍵 - 新建資料夾)