1. 程式人生 > >Java系列之代理模式

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產生代理物件,並對真實的角色進行訪問。

基本步驟:

  1. 定義一個介面IGamePlayer
  2. 定義一個被代理類,實現介面IGamePlayer
  3. 使用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