代理模式

代理類示例

  • 以前很喜歡打遊戲,和隊友們一起打怪升級,那麼通過一段簡單的代碼來表示我們打遊戲的過程。
    在這裏插入圖片描述

  • 代碼如下:首先是一個遊戲過程的接口類

public interface IGamePlayer {
    // 登錄遊戲
    void login(String user, String pwd);
    // 殺怪
    void killBoss();
    // 升級
    void upgrade();
}

  • 接着是具體遊戲玩家
public class GamePalyer implements IGamePlayer {
    // 遊戲賬戶名
    private String userName;

    public GamePalyer(String userName) {
        this.userName = userName;
    }

    @Override
    public void login(String user, String pwd) {
        System.out.println("登錄賬號爲:" + user + userName + "登錄成功");
    }

    @Override
    public void killBoss() {
        System.out.println(this.userName + "打boss");
    }

    @Override
    public void upgrade() {
        System.out.println("升級了。。。");
    }
}
  • 然後我們開始打遊戲
public static void main(String[] args) {
        IGamePlayer gamePlayer = new GamePalyer("張三");
        System.out.println("遊戲開始時間" + "2010年9月4日23:07:30");
        gamePlayer.login("zhangsan", "password");
        gamePlayer.killBoss();
        gamePlayer.upgrade();
        System.out.println("遊戲結束" + "2019年9月4日23:08:10");
    }
  • 輸出如下
遊戲開始時間2010年9月4日23:07:30
登錄賬號爲:zhangsan張三登錄成功
張三打boss
升級了。。。
遊戲結束2019年9月4日23:08:10

結果是我們想要的打遊戲的過程,不知道你們有沒有這種體驗,每天不停地打遊戲,腰痠背痛腿抽筋,學習成績也下降了,白天起不來,晚上睡不着的。如何解決呢,於是產生了一種職業:代練。我也找過代練爲我升級。接下來我們修改一下類圖。
在這裏插入圖片描述

  • 類圖中增加了一個GamePlayerProxy代理類,表示代練者,代練也是需要登錄、打怪、升級。所以也是實現了IGamePlayer接口。其代碼如下:
// 代理類
public class GamePlayerProxy implements IGamePlayer {
    
    private IGamePlayer gamePlayer;
    
    public GamePlayerProxy(IGamePlayer gamePlayer) {
        this.gamePlayer = gamePlayer;
    }
    // 代練登錄
    @Override
    public void login(String user, String pwd) {
        this.gamePlayer.login(user,pwd);
    }
    // 代練殺怪
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }
    // 代練升級
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }
}

  • 場景類修改如下
public static void main(String[] args) {
        IGamePlayer gamePlayer = new GamePalyer("張三");
        IGamePlayer proxy = new GamePlayerProxy(gamePlayer);
        System.out.println("遊戲開始時間" + "2010年9月4日23:07:30");
        proxy.login("zhangsan", "password");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("遊戲結束" + "2019年9月4日23:08:10");
}
  • 看起來沒有任何改變。但是已經不用自己去殺怪就可以升級了。

代理模式的定義

代理模式(Proxy Pattern)是一個使用率非常高的模式,其定義如下:

Provide a surrogate or placeholder for another object to control access to it.(爲其他對象提供 一種代理以控制對這個對象的訪問。)

代理模式的通用類圖如下:
在這裏插入圖片描述

  • 代理模式也叫做委託模式,它是一項基本設計技巧。許多其他的模式,如狀態模式、策 略模式、訪問者模式本質上是在更特殊的場合採用了委託模式,而且在日常的應用中,代理 模式可以提供非常好的訪問控制。在一些著名開源軟件中也經常見到它的身影,Retrofit就採用了代理模式。我們先看一下類圖中的三個角色的定義:
  • Subject抽象主題角色:抽象主題類可以是抽象類也可以是接口,是一個最普通的業務類型定義,無特殊要求。
  • RealSubject具體主題角色:也叫做被委託角色、被代理角色。它纔是冤大頭,是業務邏輯的具體執行者。
  • Proxy代理主題角色:也叫做委託類、代理類。它負責對真實角色的應用,把所有抽象主題類定義的方法限制 委託給真實主題角色實現,並且在真實主題角色處理完畢前後做預處理和善後處理工作。

抽象主題類代碼如下:

public interface Subject { 
//定義一個方法 public void request(); 
}

真實主題類

public class RealSubject implements Subject {
 //實現方法
 	 public void request() {
 	 //業務邏輯處理
 	  } 
 }

RealSubject是一個正常的業務實現類,代理模式的核心就在代理類上

public class Proxy implements Subject {
    //要代理哪個實現類 
    private Subject subject = null;

    // 默認被代理者 
    public Proxy() {
        this.subject = new Proxy();
    }

    //通過構造函數傳遞代理者 
    public Proxy(Object... objects) {
    }

    //實現接口中定義的方法 
    public void request() {
        this.before();
        this.subject.request();
        this.after();
    }

    //預處理 
    private void before() {
        //do something 
    }

    // 善後處理 
    private void after() {
        //do something } 
    }
}

一個代理類可以代理多個被委託者或被代理者,因此一個代理類具體代理哪個真實主題 角色,是由場景類決定的。當然,最簡單的情況就是一個主題類和一個代理類,這是最簡潔 的代理模式。在通常情況下,一個接口只需要一個代理類就可以了,具體代理哪個實現類由 高層模塊來決定,也就是在代理類的構造函數中傳遞被代理者,例如我們可以在代理類 Proxy中增加如下所示的構造函數。

public Proxy(Subject _subject){
 this.subject = _subject;
}

你要代理誰就產生該代理的實例,然後把被代理者傳遞進來,該模式在實際的項目應用 中比較廣泛。

代理模式的應用

代理模式的優點
  • 職責清晰

真實的角色就是實現實際的業務邏輯,不用關心其他非本職責的事務,通過後期的代理 完成一件事務,附帶的結果就是編程簡潔清晰。

  • 高擴展性

具體主題角色是隨時都會發生變化的,只要它實現了接口,甭管它如何變化,都逃不脫 如來佛的手掌(接口),那我們的代理類完全就可以在不做任何修改的情況下使用。

  • 智能化

以下的動態代理章節中你就會看到代理的智能化

代理模式的使用場景
爲什麼使用代理模式,類似於打遊戲找代練,打官司找律師,蓋房子找開發商。目的就是減輕自己的負擔。

代理模式的擴展

普通代理
  • 普通代理要求客戶端只能訪問代理角色,而不能訪問真實角色。
    在這裏插入圖片描述
  • GamePlayer 的構造函數增加了 IGamePlayer 參數,代理者只要傳入代理者的名字即可
public class GamePlayer implements IGamePlayer {
    // 遊戲賬戶名
    private String userName;

    public GamePlayer(IGamePlayer gamePlayer, String userName) throws Exception {
        if (gamePlayer == null) {
            throw new Exception("不能創建真實角色");
        } else {
            this.userName = userName;
        }
    }

    @Override
    public void login(String user, String pwd) {
        System.out.println("登錄賬號爲:" + user + userName + "登錄成功");
    }

    @Override
    public void killBoss() {
        System.out.println(this.userName + "打boss");
    }

    @Override
    public void upgrade() {
        System.out.println("升級了。。。");
    }
}


// 代理類
public class GamePlayerProxy implements IGamePlayer {

    private IGamePlayer gamePlayer;

    public GamePlayerProxy(String userName) {
        try {
            gamePlayer = new GamePlayer(this, userName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 代練登錄
    @Override
    public void login(String user, String pwd) {
        this.gamePlayer.login(user, pwd);
    }

    // 代練殺怪
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    // 代練升級
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }
}


public static void main(String[] args) {
        IGamePlayer proxy = new GamePlayerProxy("代理者");
        System.out.println("遊戲開始");
        proxy.login("zhangsan", "password");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("遊戲結束");
    }

調用者只需要知道代理類就可以了。不用知道代理了誰。該模式下,屏蔽了真實角色變更對高層模塊的影響。真實角色想怎麼修改怎麼修改。該模式非常適合要求擴展性比較高的場合。

強制代理
  • 強制代理要求必須通過真實角色找到代理角色
    在這裏插入圖片描述
public interface IGamePlayer {
    // 登錄遊戲
    void login(String user, String pwd);

    // 殺怪
    void killBoss();

    // 升級
    void upgrade();

    IGamePlayer getProxy();
}

public class GamePlayer implements IGamePlayer {
    // 遊戲賬戶名
    private String userName;
    private IGamePlayer proxy = null;

    public GamePlayer(String userName) {
        this.userName = userName;
    }

    @Override
    public void login(String user, String pwd) {
        if (this.isProxy()) {
            System.out.println("登錄賬號爲:" + user + userName + "登錄成功");
        } else {
            System.out.println("請使用代理");
        }

    }

    @Override
    public void killBoss() {
        if (this.isProxy()) {
            System.out.println(this.userName + "打boss");
        } else {
            System.out.println("請使用代理");
        }
    }

    @Override
    public void upgrade() {
        if (this.isProxy()) {
            System.out.println("升級了。。。");
        } else {
            System.out.println("請使用代理");
        }
    }

    @Override
    public IGamePlayer getProxy() {
        this.proxy = new GamePlayerProxy(this);
        return this.proxy;
    }

    private boolean isProxy() {
        return this.proxy != null;
    }
}

public class GamePlayerProxy implements IGamePlayer {

    private IGamePlayer gamePlayer;

    public GamePlayerProxy(IGamePlayer gamePlayer) {
        this.gamePlayer = gamePlayer;
    }

    // 代練登錄
    @Override
    public void login(String user, String pwd) {
        this.gamePlayer.login(user, pwd);
    }

    // 代練殺怪
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    // 代練升級
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
    }

    @Override
    public IGamePlayer getProxy() {
        return null;
    }
}

// 直接訪問真實角色類
        IGamePlayer player = new GamePlayer("張颯");
        System.out.println("遊戲開始");
        player.login("zhangsan", "password");
        player.killBoss();
        player.upgrade();
        System.out.println("遊戲結束");

    
	遊戲開始
	請使用代理
	請使用代理
	請使用代理
	遊戲結束

// 直接訪問代理角色類
        IGamePlayer player = new GamePlayer("張颯");
        IGamePlayer proxy = new GamePlayerProxy(player);
        System.out.println("遊戲開始");
        proxy.login("zhangsan", "password");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("遊戲結束");

	遊戲開始
	請使用代理
	請使用代理
	請使用代理
	遊戲結束

強制代理使用

   		IGamePlayer player = new GamePlayer("張颯");
        IGamePlayer proxy = player.getProxy();
        System.out.println("遊戲開始");
        proxy.login("zhangsan", "password");
        proxy.killBoss();
        proxy.upgrade();
        System.out.println("遊戲結束");

	遊戲開始
	登錄賬號爲:zhangsan張颯登錄成功
	張颯打boss
	升級了。。。
	遊戲結束
代理是有個性的
  • 一個類可以實現多個接口,完成不同任務的整合。代理類不僅可以實現主題類的接口,還可以實現其他接口完成不同的任務。代理的目的就是在目標方法基礎上做增強,增強的本質就是對目標方法的攔截和過濾。比如代練遊戲是要花錢的。
public interface IProxy {
    void count();
}
// 代理類
public class GamePlayerProxy implements IGamePlayer, IProxy {

    IGamePlayer gamePlayer;

    public GamePlayerProxy(IGamePlayer gamePlayer) {
        this.gamePlayer = gamePlayer;
    }

    // 代練登錄
    @Override
    public void login(String user, String pwd) {
        this.gamePlayer.login(user, pwd);
    }

    // 代練殺怪
    @Override
    public void killBoss() {
        this.gamePlayer.killBoss();
    }

    // 代練升級
    @Override
    public void upgrade() {
        this.gamePlayer.upgrade();
        this.count();
    }

    @Override
    public void count() {
        System.out.println("升級的費用爲100元");
    }
}



動態代理
  • 什麼是動態代理?動態代理是在實現階段不用關心代理誰,而在運行階段 才指定代理哪一個對象。,現在有一個非常流行的名稱叫做面向橫切面編程,也就是AOP(Aspect Oriented Programming),其核心就是採用了動態代理機制。

在這裏插入圖片描述

  • InvocationHandler 是JDK中提供的動態代理接口,對被代理類的方法進行代理。其中invoke方法是必須實現的,它完成真實方法的調用。
public class GamePlayerIH implements InvocationHandler {
    // 被代理者
    Class clazz = null;
    // 被代理實例
    Object object = null;

    public GamePlayerIH(Object object) {
        // 我要代理誰
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(this.object, args);
    }
}

  • 接下來是場景類,既沒有實現IGamePlayer也沒有創建代理類。這就是動態代理。
 public static void main(String[] args) {
        IGamePlayer gamePlayer = new GamePlayer("張三");
        InvocationHandler invocationHandler = new GamePlayerIH(gamePlayer);
        System.out.println("開始時間是 早上十點");
        ClassLoader cl = gamePlayer.getClass().getClassLoader();
        IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(cl,new Class[]{IGamePlayer.class},invocationHandler);
        proxy.login("張三","pwd");
        proxy.upgrade();
        proxy.killBoss();
        System.out.println("結束時間晚上十點");
    }
  • 如果想讓用戶登錄後給自己一個消息提醒怎麼做
public class GamePlayerIH implements InvocationHandler {
    // 被代理者
    Class clazz = null;
    // 被代理實例
    Object object = null;

    public GamePlayerIH(Object object) {
        // 我要代理誰
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equalsIgnoreCase("login")){
            System.out.println("有人在登錄賬號");
        }
        return method.invoke(this.object, args);
    }
}

這就是AOP編程。

  • 接下來我們看看動態代理的模型
    在這裏插入圖片描述

動態代理實現代理的職責,業務邏輯Subject實現相關邏輯功能。通知Advice從另一個切面切入,最終在Client進行耦合。完成封裝任務。

/**
 * 抽象主題類
 */
public interface Subject {
    void doSomething(String str);
}
/**
 * 真實主題類
 */
public class RealSubject implements Subject {
    @Override
    public void doSomething(String str) {
        System.out.println("do Something - - - > " + str);
    }
}

/**
 * 動態代理類
 */
public class MyInvocationHandler implements InvocationHandler {
    // 被代理的實例
    private Object object;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 執行被代理的方法
        return method.invoke(object,args);
    }
}
/**
 * 動態代理類
 * @param <T>
 */
public class DynamicProxy<T> {
    public static <T> T newProxyInstance(ClassLoader classLoader, Class<?>[] interfaces, InvocationHandler handler) {
        if (true) {
            // 執行通知
            new BeforeAdvice().exec();
        }
        return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

下面是實現的簡單AOP行爲 Advice

/**
 * 通知接口
 */
public interface IAdvice {
    void exec();
}

/**
 * 通知實現類
 */
public class BeforeAdvice implements IAdvice {
    @Override
    public void exec() {
        System.out.println("我是前置通知。。。");
    }
}

業務調用

 public static void main(String[] args) {
        Subject subject = new RealSubject();
        InvocationHandler handler = new MyInvocationHandler(subject);
        // 此方法生成了一個新的對象,getInterfaces() 查找所有的方法 並讓 handler 接管這些所有方法
        Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);
        proxy.doSomething("Finish");
    }

動態調用過程如下:
在這裏插入圖片描述
上面代碼還有擴展的餘地–>封裝具體業務的代理類

public class SubjectProxy extends DynamicProxy {
    public static <T> T newProxyInstance(Subject subject) {
        ClassLoader classLoader = subject.getClass().getClassLoader();
        Class<?>[] interfaces = subject.getClass().getInterfaces();
        InvocationHandler handler = new MyInvocationHandler(subject);
        return newProxyInstance(classLoader, interfaces, handler);
    }
}

實際調用會更簡單

    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject proxy = SubjectProxy.newProxyInstance(subject);
        proxy.doSomething("Finish");
    }
發佈了120 篇原創文章 · 獲贊 28 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章