在学习设计模式前,建议对设计模式的六大原则有所了解。六大原则是指导方针,设计模式则是适用于不同场景的指导方针的具体实现。在文章观察者模式中对六大原则有简单的介绍,这里不再重复阐述。
为了更好去理解代理模式的思想,下面会讲到一个现实生活中的小栗子。大部分游戏爱好者应该知道,在游戏界有个灰色的产业叫做“代练”。尽管咱们对游戏如痴如醉,反复雕琢技术,提高意识,可能仍然很难成为游戏金字塔那一小撮人。于是游戏代练就产生了,我们把自己的号交给代练,让代练代替咱们打游戏。
在代理模式中,“我们”属于被代理者,“代练”属于代理者。代练(代理者)不仅可以帮我们(被代理者)提升段位,还可以提升特定英雄的熟练度。如果我们(被代理者)希望把游戏段位打低一点,只要事先约定好(同一个接口),代练(代理者)无需改变自己也可实现。
Talk is cheap, let me show you code,直接看代码了解更直观。
基本步骤:
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,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