1. 程式人生 > >你不想幹我幫你——代理模式

你不想幹我幫你——代理模式

www. 客戶端 因為遠程 分析 新的 ogg logs mes 其他

關鍵字:設計模式,代理模式,proxy,保護代理,虛擬代理,遠程代理,緩沖代理,智能引用代理

代理模式

代理模式:給某一個對象提供一個代理或占位符,並由代理對象來控制對原對象的訪問。

說白了,就是當你不能直接訪問一個對象時,通過一個代理對象來間接訪問,這種方式就叫做代理模式。

應用場景

代理模式是一種比較常見的結構型設計模式,按照不同的場景,又可以分為保護代理,遠程代理,虛擬代理,緩沖代理和智能引用代理。

  • 保護代理,就是為原對象設置不同的訪問權限,處於被保護的狀態,不可直接訪問,用戶在訪問時根據自己不同的權限來訪問。
  • 遠程代理,在本地創建一個遠程對象的本地代理,通過訪問該本地代理實現訪問遠程對象的目的,也叫大使(Ambassador)
  • 虛擬代理,虛擬的意思是假的,當要創建一個復雜巨大的對象時,先創建一個小的代理對象,當具體使用時再創建真實對象,有點“懶加載”的意思。
  • 緩沖代理,為針對原對象的某種操作提供一個臨時的緩沖代理,用來分擔訪問原對象的壓力,以便多客戶端快速共享這些資源。
  • 智能引用代理,相當於對原對象的一種功能擴展,在訪問原對象時,加入了新功能,例如統計訪問次數等。

UML類圖分析

通過上面的定義介紹,我想我們對代理模式已經有了初步的認識,心中已經有了一種架構圖的出現:

技術分享圖片

Client無法直接訪問Real,這時出現了一個Proxy類繼承自Real,Client可以直接訪問Proxy來使用Real的資源。看上去是這樣,但是Proxy和Real不應該是繼承的關系。

為什麽Proxy不能簡單的繼承Real來達到代理模式的設計?

來看一下上面介紹過的代理模式的應用場景,我們希望通過代理模式的架構來給原對象創建一個代理類Proxy,它可以為原對象提供不同的訪問權限,可以繼承一部分原對象開放出來的接口,可以為原對象增添新功能,可以只是一個原對象的創建方法,作為原對象的一個過度,而這些,不能局限於一個基於強連接的直接繼承關系。下面我用一段代碼來說明Proxy不能簡單繼承Real的原因

package pattern.proxy;

public class Real {
    private long id;
    private String password;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}
package pattern.proxy;

public class Proxy extends Real {

}
package pattern.proxy;

import org.junit.Test;

public class Client {
    private Proxy proxy = null;

    @Test
    public void testSimpleProxy() {
        proxy = new Proxy();
        proxy.getId();
        proxy.getPassword();// 問題出現了
    }
}

發現了沒有,如果是保護代理的話,我們不希望Real類中的password屬性被透明訪問,只有擁有該權限的客戶端才可以訪問,其他都無法訪問password字段內容。所以,我們需要一個更加靈活的Proxy和Real之間的關系,這個關系一定不是強繼承的關系,經過這一番思考,我們發現了代理模式中的核心類:抽象代理類

技術分享圖片

代理模式的角色

現在來區分代理模式的角色:

  • 抽象代理類,推薦使用抽象類的方式,聲明了Real和Proxy的共同接口,這樣用來在任何使用Real的地方都可以使用Proxy。客戶端要針對該抽象代理類進行編程。
  • 具體代理類,根據合成復用原則,它包含了Real的引用,從而可以在任何時候操作Real對象,同時它與Real都是繼承自抽象代理類,並且與Real擁有相同的接口,可以隨時替換Real。然後,它可以實現對Real的控制,可以編程創建和刪除Real對象,通常來講,Client通過具體代理類訪問Real的接口時,要在具體代理類中預先執行多個補充方法,然後再對Real進行操作。
  • 真實類,Real類,Client不能或者不想直接訪問的類。它的對應於Proxy那個共同的接口有著真實的業務操作。

下面來看代碼:

Real類改為繼承Proxy抽象類,其他不變,Proxy抽象類改為

package pattern.proxy;

public abstract class Proxy {
    public abstract long getId();

    public abstract String getPassword();
}

增加一個同樣繼承於Proxy類的具體實現類,這裏通過與Real建立組合關系將Real的對象作為SuperProxy的成員屬性,為了滿足多線程要求,這裏采用了餓漢單例模式。

package pattern.proxy;

public class SuperProxy extends Proxy {
    private static Real real = new Real();// 直接采用餓漢單例

    /**
     * 要想操作Real,要先執行具體Proxy類中的一些其他方法,或許是創建Real對象,也或許是準備數據。
     */

    static {
        real.setId(12312l);
        real.setPassword("dontknow");
    }

    @Override
    public long getId() {
        return real.getId();
    }

    @Override
    public String getPassword() {
        return real.getPassword();
    }

}

客戶端調用方法:

package pattern.proxy;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;

public class Client {
    private final static Logger logger = LogManager.getLogger();
    private Proxy proxy = null;

    @Test
    public void testSimpleProxy() {
        proxy = new SuperProxy();
        long id = proxy.getId();
        String pwd = proxy.getPassword();
        logger.info("id:" + id + " password:" + pwd);
    }
}

輸出:20:35:10[testSimpleProxy][main]: id:12312 password:dontknow

業務實例(保護代理和智能引用代理)

業務需求:Client在訪問Real的時候,要先進行身份驗證並且記錄訪問次數。而同時,不可對Real內部進行修改。

這種情況下,我們可以通過代理模式增加代理類來實現,下面看代碼:

首先定義一個代理抽象基類,加入共有抽象方法(此方法一定是已存在與RealSearcher的,因為RealSearcher內部不可改變)

package pattern.proxy.search;

public abstract class Searcher {
    public abstract String doSearch(String username, int sid);
}

將原RealSearcher改為繼承於代理代理抽象基類,其他不必做任何修改。

package pattern.proxy.search;

import java.util.HashMap;
import java.util.Map;

public class RealSearcher extends Searcher {
    private Map<Integer, String> data = new HashMap<Integer, String>();

    RealSearcher() {// 模仿數據源,對象構造時初始化數據
        data.put(1001, "fridge");
        data.put(1002, "book");
        data.put(1003, "macrowave oven");
    }

    public String doSearch(String username, int sid) {
        return data.get(sid);
    }
}

此時,創建一個新的具體代理類,加入用戶校驗和訪問次數功能。

package pattern.proxy.search;

public class ProxySearcher extends Searcher {

    private Searcher searcher = new RealSearcher();

    private int count;

    public String doSearch(String username, int sid) {
        if (validateUser(username)) {
            count++;
            return "times: " + count + " " + searcher.doSearch(username, sid);
        }
        return "Identity discrepancy";
    }

    private boolean validateUser(String username) {
        if ("jhon".equals(username))
            return true;
        return false;
    }
}

最後是客戶端調用的方式,因為具體代理類和真實對象都是繼承於代理抽象基類,因此可以創建抽象基類的不同子類的實例,同時他們都擁有原屬於真實對象的查詢方法。

package pattern.proxy.search;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;

public class Client {
    private final static Logger logger = LogManager.getLogger();

    @Test
    public void testSearcher() {
        Searcher searcher = new ProxySearcher();// 創建一個代理類對象,而不是RealSearcher
        logger.info(searcher.doSearch("jhon", 1002));
        logger.info(searcher.doSearch("jhon", 1001));
        logger.info(searcher.doSearch("jack", 1002));
        logger.info(searcher.doSearch("java", 1003));
    }
}

輸出結果:

11:06:03[testSearcher][main]: times: 1 book
11:06:03[testSearcher][main]: times: 2 fridge
11:06:03[testSearcher][main]: Identity discrepancy
11:06:03[testSearcher][main]: Identity discrepancy

通過結果可以看出,我們只改變了真實查找類的繼承關系即可實現增加用戶驗證和訪問次數的功能。

其他代理場景

我們所有的代理模型都可以采用上面給出的類圖結構,上面的代碼實例介紹了保護代理和智能引用代理。下面再介紹一下其他的代理場景。

  • 遠程代理,上面簡單介紹了一下,但其實遠程代理在實際工作中有著廣泛的應用,因為我們的程序往往需要遠程主機的支持,或許是因為遠程主機有著自己的服務接口,也或許遠程主機有更加高效的環境,而遠程代理可以將本地與遠程主機直接的網絡通信相關的操作封裝起來,讓我們本地的程序可以直接使用遠程主機的好的性能或者功能。這部分由於模仿起來不好實現,因此如果未來在研究源碼時遇到遠程代理相關的內容會再詳細介紹它在代碼中具體的運用。
  • 虛擬代理同樣有著廣泛的使用。在異步通信中,它可以顯著解決由於對象本身復雜性,消耗大量系統資源或者網絡原因造成的對象加載時間過久的問題,這時候可以通過多線程技術,使一個線程加載一個速度較快的小對象來代替原對象承接客戶端新的操作,其他線程則繼續加載原對象。
  • 緩沖代理也是非常實用的模型,它可以建立一個臨時內存空間存儲那些操作較頻繁的結果,使得更多的客戶端可以直接共享這些結果而不必再去重復查找。其實我們在上面的代碼中也無意間使用到了緩沖代理,只是沒完全按照代理模式的架構去寫,那就是RealSearcher的數據初始化部分,其實實際工作中數據都是來自於真實的數據源,例如數據庫,或者網絡通信,而我們這裏相當於直接將數據源中的數據緩存在了本地程序中,當jvm啟動的時候會在內存中創建這些數據,而jvm終止以後,這些數據也就沒了,通過緩沖代理,客戶端可以獲得更快的訪問速度,同時也減少了對真實數據源的訪問次數。

代理模式總結

代理模式是非常常用的結構型設計模式,尤其是我們前面介紹過的保護代理,遠程代理,虛擬代理,緩沖代理以及智能引用代理,本文介紹了他們的主旨思想,給出了代理模式的核心架構,解釋了代理模式的原理,未來在其他內容的研究過程中,會碰到真實場景中的代理模式的應用,我會深入介紹。

你不想幹我幫你——代理模式