目录
1.游戏代练
现在的游戏代练公司非常多,我们可以把自己的账号交给代练人员,由他们去帮我们升级打怪。我们来修改一下类图:
实例代码如下:
IGamePlayer接口:
public interface IGamePlayer {
/**
* 游戏登录
*
* @param name
* @param password
*/
void login(String user, String password);
/**
* 杀怪
*/
void killBoss();
/**
* 升级
*/
void upgrade();
}
GamPlayer实现类:
public class GamePlayer implements IGamePlayer {
private String name;
public GamePlayer(String name) {
this.name = name;
}
@Override
public void login(String user, String password) {
System.out.println(user + "登录成功");
}
@Override
public void killBoss() {
System.out.println(this.name + "在打怪");
}
@Override
public void upgrade() {
System.out.println(this.name + "升了一级");
}
}
代理类:
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
客户端场景类:
public class Client {
public static void main(String[] args) {
IGamePlayer player = new GamePlayer("张三");
IGamePlayer proxy = new GamePlayerProxy(player);
proxy.login("张三", "123456");
proxy.killBoss();
proxy.upgrade();
}
}
2.代理模式
代理模式是一个使用率非常高的模式,其定义如下:为其他对象提供一种代理以控制对这个对象的访问。代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了代理模式,而且在日常应用中,代理模式可以提供非常好的访问控制。我们熟知的Spring AOP就是一个非常典型的动态代理。
2.1 代理模式的优点
- 职责清晰:真实的角色就是实现实际的业务逻辑,不用关系其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
- 高扩展性:具体主题角色是随时都会发生变化的,只要它实现了接口,无论它如何变化,那我们的代理类完全就可以在不做任何修改的情况下使用。
- 智能化
2.2 代理模式的扩展
2.2.1 普通代理
普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的。这样的解释还是比较复杂,我们还是用实例来讲解。
首先说普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色。假设,玩家不再玩游戏,完全委托给代理,我们修改类图如下:
改动很小,仅仅是修改了两个实现类的构造函数,GamePlayer的构造函数增加了gamePlayer参数,而代理角色则只要传入代理者名字即可。实现代码如下:
public class GamePlayer implements IGamePlayer {
private String name;
public GamePlayer(IGamePlayer gamePlayer, String name) throws Exception {
if (!gamePlayer.getClass().equals(GamePlayerProxy.class)) {
throw new Exception("不能创建真实角色,必须使用代理");
}
this.name = name;
}
@Override
public void login(String user, String password) {
System.out.println(user + "登录成功");
}
@Override
public void killBoss() {
System.out.println(this.name + "在打怪");
}
@Override
public void upgrade() {
System.out.println(this.name + "升了一级");
}
}
代理类实现如下:
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(String name) {
try {
gamePlayer = new GamePlayer(this, name);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
场景类:
public class Client {
public static void main(String[] args) {
IGamePlayer proxy = new GamePlayerProxy("张三");
proxy.login("张三", "123456");
proxy.killBoss();
proxy.upgrade();
}
}
张三登录成功
张三在打怪
张三升了一级
在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的角色想怎么改就怎么改,对高层模块没有任何影响。该模式非常适合对扩展性要求较高的场合。
2.2.2 强制代理
一般的思维都是通过代理找到真实的角色,而强制代理要求必须通过真实角色查找到代理角色,然后由代理角色来访问真实角色。实现类图如下;
强制代理的接口类:
public interface IGamePlayer {
/**
* 游戏登录
*
* @param name
* @param password
*/
void login(String user, String password);
/**
* 杀怪
*/
void killBoss();
/**
* 升级
*/
void upgrade();
/**
* 每个角色都能找到自己的代理
*
* @return
*/
IGamePlayer getProxy();
}
仅仅增加了一个getProxy方法,指定要访问自己必须通过哪个代理。强制代理的真实角色代码如下:
public class GamePlayer implements IGamePlayer {
private String name;
private IGamePlayer proxy;
public GamePlayer(String name) {
this.name = name;
}
/**
* 找到自己的代理
*
* @return
*/
@Override
public IGamePlayer getProxy() {
this.proxy = new GamePlayerProxy(this);
return this.proxy;
}
@Override
public void login(String user, String password) {
if (isProxy()) {
System.out.println(user + "登录成功");
}
}
@Override
public void killBoss() {
if (isProxy()) {
System.out.println(this.name + "在打怪");
}
}
@Override
public void upgrade() {
if (isProxy()) {
System.out.println(this.name + "升了一级");
}
}
/**
* 校验是否是代理访问
*
* @return
*/
private boolean isProxy() {
return this.proxy != null;
}
}
增加了一个私有方法,检查是否是自己指定的代理,是指定的代理则允许访问,否则不允许访问。强制代理的代理类:
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
/**
* 代理的代理暂时还没有,就是自己
*
* @return
*/
@Override
public IGamePlayer getProxy() {
return this;
}
}
代理的角色也可以再次被代理,这里我们没有继续延续下去,查找代理的方法就返回自己的实例。强制代理的实现场景类:
public class Client {
public static void main(String[] args) {
//定义一个游戏的角色
IGamePlayer gamePlayer = new GamePlayer("张三");
//获得指定的代理
IGamePlayer proxy = gamePlayer.getProxy();
proxy.login("张三", "123456");
proxy.killBoss();
proxy.upgrade();
}
}
强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理由真实角色来控制。
一个类可以实现多个接口,完成不同任务的整合。也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标方法进行拦截和过滤。代理类可以为真实角色预处理消息,过滤消息,消息转发,事后处理消息等功能。
3.动态代理
动态代理是在实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个对象。现在有一个非常流行的名称叫做面向切面编程(AOP),其核心就是采用了动态代理机制。动态代理类图修改如下:
在类图中增加了一个InvocationHandler接口和GamePlayH类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。相关代码定义如下:
package com.martin.learn.patterns.chapter12;
/**
* @author: martin
* @date: 2019/9/26 22:27
* @description:
*/
public interface IGamePlayer {
/**
* 游戏登录
*
* @param user
* @param password
*/
void login(String user, String password);
/**
* 杀怪
*/
void killBoss();
/**
* 升级
*/
void upgrade();
}
package com.martin.learn.patterns.chapter12;
/**
* @author: martin
* @date: 2019/9/26 22:29
* @description:
*/
public class GamePlayer implements IGamePlayer {
private String name;
private IGamePlayer proxy;
public GamePlayer(String name) {
this.name = name;
}
@Override
public void login(String user, String password) {
System.out.println(user + "登录成功");
}
@Override
public void killBoss() {
System.out.println(this.name + "在打怪");
}
@Override
public void upgrade() {
System.out.println(this.name + "升了一级");
}
}
代理类:
package com.martin.learn.patterns.chapter12;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author: martin
* @date: 2019/10/13 16:31
* @description:
*/
public class GamePlayIH implements InvocationHandler {
/**
* 被代理者
*/
private Class clazz;
/**
* 被代理的实例
*/
private Object object;
//我要代理谁
public GamePlayIH(Object object) {
this.object = object;
}
//调用被代理的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.print("代理操作:");
return method.invoke(this.object, args);
}
}
实现的场景类Client:
package com.martin.learn.patterns.chapter12;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author: martin
* @date: 2019/9/28 20:31
* @description:
*/
public class Client {
public static void main(String[] args) {
//定义一个游戏的玩家
IGamePlayer gamePlayer = new GamePlayer("张三");
//定义一个Handler
InvocationHandler handler = new GamePlayIH(gamePlayer);
//获得类的ClassLoader
ClassLoader classLoader = gamePlayer.getClass().getClassLoader();
//动态产生一个代理者
IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(classLoader, new Class[]{IGamePlayer.class}, handler);
proxy.login("张三", "123456");
proxy.killBoss();
proxy.upgrade();
}
}
代码执行结果输出如下:
代理操作:张三登录成功
代理操作:张三在打怪
代理操作:张三升了一级
其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。如果我们想让游戏登录之后发一个消息给我们,防止盗号,该如何修改?我们可以直接修改动态代理类:
package com.martin.learn.patterns.chapter12;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author: martin
* @date: 2019/10/13 16:31
* @description:
*/
public class GamePlayIH implements InvocationHandler {
/**
* 被代理者
*/
private Class clazz;
/**
* 被代理的实例
*/
private Object object;
//我要代理谁
public GamePlayIH(Object object) {
this.object = object;
}
//调用被代理的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.print("代理操作:");
Object result = method.invoke(this.object, args);
if (method.getName().equalsIgnoreCase("login")) {
System.out.println("有人在用我的账号登录!");
}
return result;
}
}
当有人用我的账号登录时,就发送一个消息,然后看看自己的账号是不是被人盗取,非常有用的方法,这就是AOP编程。AOP编程没有使用什么新的技术,但是它对我们的设计、编码都有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方法切入进去。
4.最佳实战
代理模式应用的非常广泛,比如Spring AOP和AspectJ这样非常优秀的框架都是使用的代理模式。在学习这些优秀的框架时,需要弄清楚切面、切入点、通知和织入这些名词。