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();
	}
}

打印结果:

Java系列之代理模式_第1张图片

代理模式特点:可以通过访问代理类,间接的访问真实的角色(被代理类)。

高扩展性:只要代理类和真实类实现同一个接口,不管真实类的逻辑如何变化,代理类都不需要做出修改,提高了程序的扩展性。

职责清晰:一件事物由代理类完成,代理可以对逻辑进行扩展,而真实类结构不会受其影响,只需关注自己的业务逻辑(例如上面的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

 

你可能感兴趣的:(java)