设计模式-代理模式(Proxy Pattern)
一、定义
为其他对象提供一种代理以控制对这个对象的访问。
二、概念解释
就是对真实对象的包装,通过包装,不仅能代理实现真实对象提供的服务,还能在提供服务的各个时间节点实现额外的附加功能
三、场景
暗黑破坏神3是一款非常肝的游戏,里面的单个装备最好的就是前缀有太古修饰的,以前玩的时候为了刷太古,不知肝过了多少个春夏秋冬,现在想想这种重复的肝副本我完全可以找个代练帮我肝,这样我能省下大把的时间和留个好肝
四、实现
1、类图
2、代码实现
首先定义一个游戏接口
public interface DiabloThree {
/**
* 刷超级装备
*/
void getSuperEquipment();
}
我是真实玩家
public class Player implements DiabloThree{
@Override
public void getSuperEquipment() {
System.out.println("不出太古不睡觉");
}
}
代练
public class PlayerProxy implements DiabloThree{
private Player player;
public PlayerProxy(Player player) {
this.player = player;
}
@Override
public void getSuperEquipment() {
checkUserCount();
player.getSuperEquipment();
recordGetSuperEquipmentDate();
}
private void checkUserCount() {
System.out.println("检查下用户的装备刷不刷的动怪");
}
private void recordGetSuperEquipmentDate() {
System.out.println("记录下时间 好知道平均多少年出一件太古");
}
}
先自己刷刷到困为止,然后让代练继续刷
@SpringBootTest
public class ProxyTest {
@Test
public void test() {
// 我在刷
Player player = new Player();
player.getSuperEquipment();
// 刷不动了 找代练
PlayerProxy playerProxy = new PlayerProxy(player);
// 代练刷
playerProxy.getSuperEquipment();
}
}
五、小结
以上例子,代练这个代理类是我通过编码在编译前就定义好了的,这种代理就叫做静态代理,很明显静态代理太局限,当前代练在刷装备前检查了账号强度,在刷装备后做了时间记录,如果有另外一个代练,他在刷装备前是不检查账号强度的,而是检查你有没有足够的刷副本的钥匙,然后刷完装备后也不记录,而是截图,那我就得再硬编码一个这样的代练出来,如果又有别的代练在刷装备前后有不同的操作,那就得再敲代码。这种方式很明显笨拙,那有没有我不用自己写代理类,而是可以根据我的需要在程序运行时自动生成符合条件的代理类,答案是肯定的,这种方式就是动态代理
六、动态代理实现
动态代理常用的实现方式有两种,一种是java通过Proxy类和InvocationHandler接口实现的基于实现接口的动态代理,一种是cglib通过其增强器Enchancer基于继承实现的动态代理,本文仅演示java基于接口实现的动态代理
代理类要执行的方法必须实现InvocationHandler接口中的invoke方法 这里我们针对上述的游戏例子 生成一个检查账号的invocationHandler
public class CheckPlayerAccountInvocationHandler<T> implements InvocationHandler {
T target;
public CheckPlayerAccountInvocationHandler(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
checkUserCount();
Object o = method.invoke(target, args);
recordGetSuperEquipmentDate();
return o;
}
private void checkUserCount() {
System.out.println("检查下用户的装备刷不刷的动怪");
}
private void recordGetSuperEquipmentDate() {
System.out.println("记录下时间 好知道平均多少年出一件太古");
}
}
使用Proxy类动态生成代理类
@Test
public void DynamicProxyTest() {
// 真实玩家
Player player = new Player();
// 代理类定义的代理方法
CheckPlayerAccountInvocationHandler<Player> usePlayerAccountInvocationHandler = new CheckPlayerAccountInvocationHandler<>(player);
// newProxyInstance生成代理类
// 第一个参数指定使用哪个类加载器将代理类加载到JVM 一般选择跟被代理对象一样的类加载器
// 第二个参数指定代理类要实现的接口列表
// 第三个参数将方法调用分派到自己实现的调用处理程序
DiabloThree proxy = (DiabloThree) Proxy.newProxyInstance(player.getClass().getClassLoader(), new Class<?>[]{DiabloThree.class}, usePlayerAccountInvocationHandler);
proxy.getSuperEquipment();
}
六 总结
在实际的开发项目中,该模式最好的体现就是Spring的aop,基于动态代理,aop可以为我们写的接口做统一鉴权、统一日志记录等等,而不需要我们在每一个接口中去做这种重复的逻辑,给我们的开发工作提供了极大的便利