「补课」进行时:设计模式(5)——从 LOL 中学习代理模式
1. 前文汇总
2. 从 LOL 中学习代理模式
我是一个很喜欢玩游戏的人,虽然平时玩游戏的时间并不多,但我也是一个忠实的 LOL 的爱好者,就是段位有点惨不忍睹,常年倔强的黑铁,今年 S10 的总决赛在上海举行,这个事儿我从 S9 就开始期待,结果门票今年没卖,直接是抽签拼人品。
360w+ 人抽 3600+ 人,这个概率属实有点低,只能找个地方和我的小伙伴一起看了。
打 LOL 最开心的事情莫过于拿到 PentaKill 和 victory ,把这件事情使用代码表现出来,首先定义一个玩游戏的人的接口:
public interface ILOLPlayer {
// 登录使用用户名和密码
void login(String name, String password);
// 拿到五杀
void pentaKill();
// 游戏胜利
void victory();
}
第二步对上面的接口做一个实现:
public class LOLPlayer implements ILOLPlayer {
private String name = "";
public LOLPlayer(String name) {
this.name = name;
}
@Override
public void login(String name, String password) {
System.out.println("登录游戏:name:" + name + ", password:" + password);
}
@Override
public void pentaKill() {
System.out.println(this.name + " 拿到五杀啦!!!");
}
@Override
public void victory() {
System.out.println(this.name + " 游戏胜利啦!!!");
}
}
最后我们写一个最简单的测试类:
public class Test {
public static void main(String[] args) {
LOLPlayer lolPlayer = new LOLPlayer("geekdigging");
lolPlayer.login("geekdigging", "password");
lolPlayer.pentaKill();
lolPlayer.victory();
}
}
运行结果:
登录游戏:name:geekdigging, password:password
geekdigging 拿到五杀啦!!!
geekdigging 游戏胜利啦!!!
在打游戏的过程中,大家都知道有一个类型叫做排位赛,排位赛能到多少段位,一个是看时间,一个是看天赋,基本上打到一定的段位就很难再往上走了,如果说这时候还想升段位,那就只能取找代练帮忙做代打了。
我们找一位代练帮我们继续打游戏:
public class LOLPlayerProxy implements ILOLPlayer {
private ILOLPlayer ilolPlayer;
public LOLPlayerProxy(LOLPlayer playerLayer) {
this.ilolPlayer = playerLayer;
}
@Override
public void login(String name, String password) {
this.ilolPlayer.login(name, password);
}
@Override
public void pentaKill() {
this.ilolPlayer.pentaKill();
}
@Override
public void victory() {
this.ilolPlayer.victory();
}
}
我们稍微修改一下测试类:
public class Test {
public static void main(String[] args) {
LOLPlayer lolPlayer = new LOLPlayer("geekdigging");
LOLPlayerProxy proxy = new LOLPlayerProxy(lolPlayer);
proxy.login("geekdigging", "password");
proxy.pentaKill();
proxy.victory();
}
}
这个测试类里面,我们没有自己打游戏,而是使用代练 proxy 来帮我们打游戏,最后的结果是:
登录游戏:name:geekdigging, password:password
geekdigging 拿到五杀啦!!!
geekdigging 游戏胜利啦!!!
这就是代理模式,本来需要自己做事情,使用代理以后,就可以由代理帮我们做事情了。
3. 代理模式定义
代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义如下:
Provide a surrogate or placeholder for another object to control access toit.(为其他对象提供一种代理以控制对这个对象的访问。)
- Subject: 抽象主题角色。
- RealSubject: 具体主题角色。
- Proxy: 代理主题角色。
通用示例代码如下:
// 抽象主题类,定义一个方法
public interface Subject {
void request();
}
// 具体主题类,在这里写具体的处理逻辑
public class RealSubject implements Subject {
@Override
public void request() {
// 逻辑处理
}
}
// 代理类
public class Proxy implements Subject {
private Subject subject;
public Proxy() {
this.subject = new Proxy();
}
public Proxy(RealSubject subject) {
this.subject = subject;
}
@Override
public void request() {
this.before();
this.subject.request();
this.after();
}
private void before() {
// 逻辑预处理
}
private void after() {
// 逻辑善后处理
}
}
在最后的这个代理类中,通过构造函数来进行代理角色的传递,同时还可以在具体的处理逻辑上构造一个切面,定义预处理逻辑以及善后处理逻辑。
4. 代理模式的优点
- 职责清晰:真实的角色是用来实现具体业务逻辑的,无需关心其他工作,可以后期通过代理的方式来完成其他的工作。
- 高扩展性:
- 智能化:
5. 普通代理
首先说普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色,这是比较简单的。
使用上面最开始的打 LOL 进行改造,我自己作为一个游戏玩家,我肯定自己不练级了,也就是场景类不能再直接 new 一个 LOLPlayer 对象了,它必须由 LOLPlayerProxy 来进行模拟场景。
首先是对 LOLPlayer 类进行改造,把 LOLPlayer 这个类的构造方法修改,使他不能直接 new 一个对象出来。
public class LOLPlayer implements ILOLPlayer {
private String name;
public LOLPlayer(ILOLPlayer ilolPlayer, String name) throws Exception {
if (ilolPlayer == null) {
throw new Exception("不能创建真实的角色");
} else {
this.name = name;
}
}
// 省略剩余的代码
}
接下来是代理类:
public class LOLPlayerProxy implements ILOLPlayer {
private ILOLPlayer iloLPlayer;
public LOLPlayerProxy(String name) {
try {
iloLPlayer = new LOLPlayer(this, name);
} catch (Exception e) {
e.printStackTrace();
}
}
// 省略剩余的代码
}
代理类也是仅修改了构造函数,通过传进来的一个代理者的名称,就能进行代理,在这种改造下,系统更加简洁了,调用者只知道代理存在就可以,不用知道代理了谁。
最后的测试类也需要进行修改:
public class Test {
public static void main(String[] args) {
ILOLPlayer proxy = new LOLPlayerProxy("geekdigging");
proxy.login("geekdigging", "password");
proxy.pentaKill();
proxy.victory();
}
}
在这个代理类上,我没有再去 new 一个 LOLPlayer 的对象,即可对 LOLPlayer 进行代理。
7. 强制代理
强制代理实际上一个普通代理模式的变种,普通代理是通过代理找到真实的角色,但是强制代理却是要「强制」,必须通过真实角色查找到代理角色,否则将不能访问。
首先是对接口类加一个 getProxy() 方法,指定要访问自己必须通过哪个代理。
public interface ILOLPlayer {
// 登录使用用户名和密码
void login(String name, String password);
// 拿到五杀
void pentaKill();
// 游戏胜利
void victory();
// 获取自己的代理类
ILOLPlayer getProxy();
}
然后再是对具体实现类的改造:
public class LOLPlayer implements ILOLPlayer {
private String name;
private ILOLPlayer proxy;
public LOLPlayer(String name) {
this.name = name;
}
@Override
public void login(String name, String password) {
if (this.isProxy()) {
System.out.println("登录游戏:name:" + name + ", password:" + password);
} else {
System.out.println("请使用指定的代理");
}
}
@Override
public void pentaKill() {
if (this.isProxy()) {
System.out.println(this.name + " 拿到五杀啦!!!");
} else {
System.out.println("请使用指定的代理");
}
}
@Override
public void victory() {
if (this.isProxy()) {
System.out.println(this.name + " 游戏胜利啦!!!");
} else {
System.out.println("请使用指定的代理");
}
}
@Override
public ILOLPlayer getProxy() {
this.proxy = new LOLPlayerProxy(this);
return this.proxy;
}
private boolean isProxy() {
if (this.proxy == null) {
return false;
} else {
return true;
}
}
}
这里增加了一个私有方法,检查是否是自己指定的代理,是指定的代理则允许访问,否则不允许访问。
接下来是强制代理类的改进:
public class LOLPlayerProxy implements ILOLPlayer {
private ILOLPlayer iloLPlayer;
public LOLPlayerProxy(ILOLPlayer iloLPlayer) {
this.iloLPlayer = iloLPlayer;
}
@Override
public void login(String name, String password) {
this.iloLPlayer.login(name, password);
}
@Override
public void pentaKill() {
this.iloLPlayer.pentaKill();
}
@Override
public void victory() {
this.iloLPlayer.victory();
}
@Override
public ILOLPlayer getProxy() {
return this;
}
}
最后一个是测试类:
public class Test {
public static void main(String[] args) {
test1();
test2();
test3();
}
public static void test1() {
ILOLPlayer iloLPlayer = new LOLPlayer("geekdigging");
iloLPlayer.login("geekdigging", "password");
iloLPlayer.pentaKill();
iloLPlayer.victory();
}
public static void test2() {
ILOLPlayer iloLPlayer = new LOLPlayer("geekdigging");
ILOLPlayer proxy = new LOLPlayerProxy(iloLPlayer);
proxy.login("geekdigging", "password");
proxy.pentaKill();
proxy.victory();
}
public static void test3() {
ILOLPlayer iloLPlayer = new LOLPlayer("geekdigging");
ILOLPlayer proxy = iloLPlayer.getProxy();
proxy.login("geekdigging", "password");
proxy.pentaKill();
proxy.victory();
}
}
这里我写了三个测试方法,分别是 test1 、 test2 和 test3 ,执行一下这个测试类,结果如下:
请使用指定的代理
请使用指定的代理
请使用指定的代理
请使用指定的代理
请使用指定的代理
请使用指定的代理
登录游戏:name:geekdigging, password:password
geekdigging 拿到五杀啦!!!
geekdigging 游戏胜利啦!!!
可以发现,前两个方法都没有正常产生访问, test1 是直接 new 了一个对象,无法成功访问,而 test2 虽然是使用了代理,但是结果还是失败了,因为它指定的并不是真实的对象,这个对象是我们自己手动 new 出来的,当然不行,只有最后一个 test3 是可以正常代理对象的。
强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用 getProxy 就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理已经由真实角色自己完成。
6. 动态代理
动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。相对来说,自己写代理类的方式就是静态代理。
实现动态代理,主要有两种方式,一种是通过 JDK 为我们提供的 InvocationHandler 接口,另一种是使用 cglib 。
把上面的案例接着改成动态代理的方式:
增加一个 LOLPlayIH 动态代理类,来实现 InvocationHandler 接口。
public class LOLPlayIH implements InvocationHandler {
Object object;
public LOLPlayIH(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(this.object, args);
return result;
}
}
这里的 invoke 方法是接口 InvocationHandler 定义必须实现的,它完成对真实方法的调用。
接下来是测试类:
public class Test {
public static void main(String[] args) {
ILOLPlayer ilolPlayer = new LOLPlayer("geekdigging");
InvocationHandler handler = new LOLPlayIH(ilolPlayer);
ClassLoader loader = ilolPlayer.getClass().getClassLoader();
ILOLPlayer proxy = (ILOLPlayer) Proxy.newProxyInstance(loader, new Class[] {ILOLPlayer.class}, handler);
proxy.login("geekdigging", "password");
proxy.pentaKill();
proxy.victory();
}
}
这里我们没有创建代理类,也没有实现 ILOLPlayer 接口,但我们还是让代练在帮我们上分,这就是动态代理。
接下来看下 CGLIB 代理的方式,修改前面的代理类:
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(final Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = methodProxy.invoke(this.target, objects);
return result;
}
}
编写新的测试类:
public class Test {
public static void main(String[] args) {
ILOLPlayer ilolPlayer = new LOLPlayer("geekdigging");
CglibProxy proxy = new CglibProxy();
LOLPlayer lolPlayer = (LOLPlayer) proxy.getInstance(ilolPlayer);
lolPlayer.login("geekdigging", "password");
lolPlayer.pentaKill();
lolPlayer.victory();
}
}
这里有一点需要注意, CGLIB 动态代理需要具体对象拥有无参构造,需要我们手动在 LOLPlayer 中添加一个无参构造函数。
「补课」进行时:设计模式(5)——从 LOL 中学习代理模式的更多相关文章
- 简介Python设计模式中的代理模式与模板方法模式编程
简介Python设计模式中的代理模式与模板方法模式编程 这篇文章主要介绍了Python设计模式中的代理模式与模板方法模式编程,文中举了两个简单的代码片段来说明,需要的朋友可以参考下 代理模式 Prox ...
- Python后端日常操作之在Django中「强行」使用MVVM设计模式
扫盲 首先带大家了解一下什么是MVVM模式: 什么是MVVM?MVVM是Model-View-ViewModel的缩写. MVVM是MVC的增强版,实质上和MVC没有本质区别,只是代码的位置变动而已 ...
- 不设目标也能通关「马里奥」的AI算法,全靠好奇心学习
在强化学习中,设计密集.定义良好的外部奖励是很困难的,并且通常不可扩展.通常增加内部奖励可以作为对此限制的补偿,OpenAI.CMU 在本研究中更近一步,提出了完全靠内部奖励即好奇心来训练智能体的方法 ...
- 设计模式(一):“穿越火线”中的“策略模式”(Strategy Pattern)
在前段时间呢陆陆续续的更新了一系列关于重构的文章.在重构我们既有的代码时,往往会用到设计模式.在之前重构系列的博客中,我们在重构时用到了“工厂模式”.“策略模式”.“状态模式”等.当然在重构时,有的地 ...
- 设计模式(十三): Proxy代理模式 -- 结构型模式
设计模式(十一)代理模式Proxy(结构型) 1.概述 因为某个对象消耗太多资源,而且你的代码并不是每个逻辑路径都需要此对象, 你曾有过延迟创建对象的想法吗 ( if和else就是不同的两条逻辑路 ...
- swift设计模式学习 - 代理模式
移动端访问不佳,请访问我的个人博客 设计模式学习的demo地址,欢迎大家学习交流 代理模式 代理模式为其他对象提供一种代理以控制对这个对象的访问,在某些情况下,一个对象不适合或者不能直接引用另一个对象 ...
- 设计模式学习——代理模式(Proxy Pattern)之 强制代理(强校验,防绕过)
上周温习了代理模式:http://www.cnblogs.com/chinxi/p/7354779.html 在此进行拓展,学习强制代理.但是发现网上大多例子都有个“天坑”(我是这么认为的),在得到代 ...
- Java 设计模式系列(十二)代理模式
Java 设计模式系列(十二)代理模式 代理模式是对象的结构模式.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. package com.github.binarylei.de ...
- JAVA设计模式 5【结构型】代理模式的理解与使用
今天要开始我们结构型 设计模式的学习,设计模式源于生活,还是希望能通过生活中的一些小栗子去理解学习它,而不是为了学习而学习这些东西. 结构型设计模式 结构型设计模式又分为 类 结构型 对象 结构型 前 ...
随机推荐
- 微信小程序结合微信公众号进行消息发送
微信小程序结合微信公众号进行消息发送 由于小程序的模板消息已经废弃了,官方让使用订阅消息功能.而订阅消息的使用限制比较大,用户必须得订阅.需要获取用户同意接收消息的权限.用户必须得和小程序有交互的时候 ...
- Spring源码系列(四)--spring-aop是如何设计的
简介 spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦.方便.在实际项目中,spr ...
- Python-属性描叙符协议ORM实现原理依据- __set__ __get__ __delete__
class CheckString: def __init__(self, variable_type): self.variable_type = variable_type def __set__ ...
- 《穷查理年鉴》习惯 & 工作 & 自省 & 自律 (关于自己)
习惯 001.在那充满古老年鉴的年代里,扔掉你的恶行,不管它们曾经给你带来多大的好处. 002.许多关于预言的争论都可以简化为:当你说是时,就有人说浊;当你认为不是时,一定有人说是. 003.坏习惯和 ...
- MLHPC 2018 | Aluminum: An Asynchronous, GPU-Aware Communication Library Optimized for Large-Scale Training of Deep Neural Networks on HPC Systems
这篇文章主要介绍了一个名为Aluminum通信库,在这个库中主要针对Allreduce做了一些关于计算通信重叠以及针对延迟的优化,以加速分布式深度学习训练过程. 分布式训练的通信需求 通信何时发生 一 ...
- .NET 云原生架构师训练营(模块一 架构师与云原生)--学习笔记
目录 什么是软件架构 软件架构的基本思路 单体向分布式演进.云原生.技术中台 1.1 什么是软件架构 1.1.1 什么是架构? Software architecture = {Elements, F ...
- mongoose 查询数据属性为数组,且包含某个值的方法
mongoose在创建schema的时候有些属性需要设置为数组类型,比如商品图片.商品标签.不同尺寸.价格等. 那么怎么查询具有某个标签的商品了,下面记录一下两种情况: 查询具有'vue'标签的文章 ...
- Python+Appium自动化测试(10)-TouchAction类与MultiAction类(控件元素的滑动、拖动,九宫格解锁,手势操作等)
滑动屏幕方法swipe一般用于对页面进行上下左右滑动操作,但自动化过程中还会遇到其他情况,如对控件元素进行滑动.拖拽操作,九宫格解锁,手势操作,地图的放大与缩小等.这些需要针对控件元素的滑动操作,或者 ...
- MeteoInfoLab脚本示例:计算垂直螺旋度
尝试编写MeteoInfoLab脚本计算垂直螺旋度,结果未经验证. 脚本程序: print 'Open data files...' f_uwnd = addfile('D:/Temp/nc/uwnd ...
- MeteoInfoLab脚本示例:创建netCDF文件(合并文件)
在MeteoInfoLab中增加了创建netCDF文件并写入数据的功能,这里利用合并多个netCDF文件为一个新的netCDF文件为例.1.创建一个可写入的netCDF文件对象(下面用ncfile表示 ...