Java系列之代理模式
一,寫在前面
在學習設計模式前,建議對設計模式的六大原則有所瞭解。六大原則是指導方針,設計模式則是適用於不同場景的指導方針的具體實現。在文章觀察者模式中對六大原則有簡單的介紹,這裡不再重複闡述。
為了更好去理解代理模式的思想,下面會講到一個現實生活中的小栗子。大部分遊戲愛好者應該知道,在遊戲界有個灰色的產業叫做“代練”。儘管咱們對遊戲如痴如醉,反覆雕琢技術,提高意識,可能仍然很難成為遊戲金字塔那一小撮人。於是遊戲代練就產生了,我們把自己的號交給代練,讓代練代替咱們打遊戲。
在代理模式中,“我們”屬於被代理者,“代練”屬於代理者。代練(代理者)不僅可以幫我們(被代理者)提升段位,還可以提升特定英雄的熟練度。如果我們(被代理者)希望把遊戲段位打低一點,只要事先約定好(同一個介面),代練(代理者)無需改變自己也可實現。
Talk is cheap, let me show you code,直接看程式碼瞭解更直觀。
二,普通代理
基本步驟:
- 定義一個介面IGamePlayer
- 定義一個被代理類,實現介面IGamePlayer
- 定義一個代理類,實現介面IGamePlayer,並持有被代理類的引用
1,介面IGamePlayer,程式碼如下:
public interface IGamePlayer { public void beginGame(); //開始遊戲 public void playGame(); //正在打遊戲 public void gameOver(); //遊戲結束 }
2,被代理類GamePlayer,程式碼如下:
public class GamePlayer implements IGamePlayer { @Override public void beginGame() { System.out.println("GamePlayer << invoke beginGame"); } @Override public void playGame() { System.out.println("GamePlayer << invoke playGame"); } @Override public void gameOver() { System.out.println("GamePlayer << invoke gameOver"); } }
3,代理類ProxyGamePlayer,程式碼如下:
//代理類和真實類實現同一個介面
public class ProxyGamePlayer implements IGamePlayer {
//代理類持有真實類的引用
private IGamePlayer gamePlayer;
public ProxyGamePlayer(IGamePlayer gamePlayer) {
super();
this.gamePlayer = gamePlayer;
}
private void doSomething() {
System.out.println("ProxyGamePlayer << doSomething");
}
@Override
public void beginGame() {
gamePlayer.beginGame();
}
@Override
public void playGame() {
gamePlayer.playGame();
}
@Override
public void gameOver() {
gamePlayer.gameOver();
doSomething();
}
}
代理類和真實類(被代理類)實現了同一個介面,遵守同一個規則。
第5行,代理類持有真實類的引用,通過成員變數實現,符合迪米特法則。
第7行,代理類和真實類兩個模組間依賴是通過抽象產生,符合依賴倒置原則。
第29行,代理類在gameOver方法裡,呼叫了真實類的gameOver方法,以及自己私有的doSomething方法。doSomething方法不是真實類需要處理邏輯,由代理類來完成,使真實類職責清晰。
4,客戶端程式碼呼叫,程式碼如下:
public class Client {
public static void main(String[] args) {
//建立真實類的例項
IGamePlayer gamePlayer = new GamePlayer();
//建立代理類,並通過建構函式產生依賴
ProxyGamePlayer proxy = new ProxyGamePlayer(gamePlayer);
//呼叫代理類的方法
proxy.beginGame();
proxy.playGame();
proxy.gameOver();
}
}
列印結果:
代理模式特點:可以通過訪問代理類,間接的訪問真實的角色(被代理類)。
高擴充套件性:只要代理類和真實類實現同一個介面,不管真實類的邏輯如何變化,代理類都不需要做出修改,提高了程式的擴充套件性。
職責清晰:一件事物由代理類完成,代理可以對邏輯進行擴充套件,而真實類結構不會受其影響,只需關注自己的業務邏輯(例如上面的doSomething方法)。
三,動態代理
動態代理:不需要提供代理類ProxyGamePlayer,卻能通過Java提供的相關的API產生代理物件,並對真實的角色進行訪問。
基本步驟:
- 定義一個介面IGamePlayer
- 定義一個被代理類,實現介面IGamePlayer
- 使用Proxy類生成一個代理物件
步驟1,2需要提供一個介面,一個實現該介面的真實角色。直接借用上面普通代理的程式碼,這裡不再重複展示程式碼。
在客戶端中生成一個代理物件,程式碼如下:
public class Client {
public static void main(String[] args) {
//建立被代理物件
final IGamePlayer player = new GamePlayer();
//建立InvocationHandler的例項
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = method.invoke(player, args);
return result;
}
};
//動態獲取代理物件
IGamePlayer proxyIntance = (IGamePlayer) Proxy.newProxyInstance(
player.getClass().getClassLoader(),//被代理類的類載入器
new Class[]{IGamePlayer.class}, //介面的Class例項
h); //InvocationHandler例項
//呼叫代理物件的方法
proxyIntance.gameOver();
}
}
第18行,動態生成一個代理物件proxyIntance。newProxyInstance第三個引數h,是一個InvocationHandler的子類例項。InvocationHandler是Java提供的一個反射介面,只有一個invoke方法。
第12行,通過反射完成被代理類裡方法的呼叫。不管是普通代理還是動態代理,都是呼叫真實角色的方法。
第一個引數:傳入被代理類的例項player;
第二個引數:傳入被代理類方法的輸入引數的型別;
第24行,使用代理物件呼叫gameOver方法。
動態代理的使用場景
試想一個這樣的場景:代練每完成一把遊戲,就需要將遊戲結果傳送給客戶。
解決方案一:在被代理類的gameOver方法中新增邏輯,但修改原有的程式碼結構並不好。
解決方案二:在InvocationHandler的invoke方法中新增邏輯。
對於方案二,注意到invoke方法的第二個引數method,通過這個反射類可以操作被代理類的所有方法。
修改後程式碼如下:
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = method.invoke(player, args);
if ("gameOver".equals(method.getName())) {
//如果是呼叫gameOver方法,則通知客戶遊戲結果
System.out.println("這把遊戲失敗啦~嘿嘿");
}
return result;
}
};
上述程式碼應該比較好理解,這裡不再闡述。
四,最後
本篇文章介紹了普通代理和動態代理的使用,閱讀本文可以對代理模式有個基本的認識。事實上代理模式的變形還是挺多的,需要我們在工程實踐中去積累,增強對代理模式的理解以便靈活應用。
O(∩_∩)O