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. 代理模式的优点

  1. 职责清晰:真实的角色是用来实现具体业务逻辑的,无需关心其他工作,可以后期通过代理的方式来完成其他的工作。
  2. 高扩展性:
  3. 智能化:

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 中学习代理模式的更多相关文章

  1. 简介Python设计模式中的代理模式与模板方法模式编程

    简介Python设计模式中的代理模式与模板方法模式编程 这篇文章主要介绍了Python设计模式中的代理模式与模板方法模式编程,文中举了两个简单的代码片段来说明,需要的朋友可以参考下 代理模式 Prox ...

  2. Python后端日常操作之在Django中「强行」使用MVVM设计模式

    扫盲 首先带大家了解一下什么是MVVM模式: 什么是MVVM?MVVM是Model-View-ViewModel的缩写. MVVM是MVC的增强版,实质上和MVC没有本质区别,只是代码的位置变动而已 ...

  3. 不设目标也能通关「马里奥」的AI算法,全靠好奇心学习

    在强化学习中,设计密集.定义良好的外部奖励是很困难的,并且通常不可扩展.通常增加内部奖励可以作为对此限制的补偿,OpenAI.CMU 在本研究中更近一步,提出了完全靠内部奖励即好奇心来训练智能体的方法 ...

  4. 设计模式(一):“穿越火线”中的“策略模式”(Strategy Pattern)

    在前段时间呢陆陆续续的更新了一系列关于重构的文章.在重构我们既有的代码时,往往会用到设计模式.在之前重构系列的博客中,我们在重构时用到了“工厂模式”.“策略模式”.“状态模式”等.当然在重构时,有的地 ...

  5. 设计模式(十三): Proxy代理模式 -- 结构型模式

      设计模式(十一)代理模式Proxy(结构型) 1.概述 因为某个对象消耗太多资源,而且你的代码并不是每个逻辑路径都需要此对象, 你曾有过延迟创建对象的想法吗 ( if和else就是不同的两条逻辑路 ...

  6. swift设计模式学习 - 代理模式

    移动端访问不佳,请访问我的个人博客 设计模式学习的demo地址,欢迎大家学习交流 代理模式 代理模式为其他对象提供一种代理以控制对这个对象的访问,在某些情况下,一个对象不适合或者不能直接引用另一个对象 ...

  7. 设计模式学习——代理模式(Proxy Pattern)之 强制代理(强校验,防绕过)

    上周温习了代理模式:http://www.cnblogs.com/chinxi/p/7354779.html 在此进行拓展,学习强制代理.但是发现网上大多例子都有个“天坑”(我是这么认为的),在得到代 ...

  8. Java 设计模式系列(十二)代理模式

    Java 设计模式系列(十二)代理模式 代理模式是对象的结构模式.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. package com.github.binarylei.de ...

  9. JAVA设计模式 5【结构型】代理模式的理解与使用

    今天要开始我们结构型 设计模式的学习,设计模式源于生活,还是希望能通过生活中的一些小栗子去理解学习它,而不是为了学习而学习这些东西. 结构型设计模式 结构型设计模式又分为 类 结构型 对象 结构型 前 ...

随机推荐

  1. spark 笔记2

    一.Spark Shuffle 的发展 Spark 0.8及以前 Hash Based Shuffle Spark 0.8.1 为Hash Based Shuffle引入File Consolidat ...

  2. springboot的启动流程源码分析

    .测试项目,随便一个简单的springboot项目即可: 直接debug调试: 可见,分2步,第一步是创建SpringApplication对象,第二步是调用run方法: 1.SpringApplic ...

  3. Python-在列表、字典中筛选数据

    实际问题有哪些? 过滤掉列表[3,9,-1,10.-2......] 中负数 筛选出字典{'li_ming':90,'xiao_hong':60,'li_kang':95,'bei_men':98} ...

  4. GAN生成的评价指标 Evaluation of GAN

    传统方法中,如何衡量一个generator ?-- 用 generator 产生数据的 likelihood,越大越好. 但是 GAN 中的 generator 是隐式建模,所以只能从 P_G 中采样 ...

  5. Layman CSS3+H5实现上下垂直居中的几种主要方法

    方法1:通过 translate 移位来实现 H5+CSS3: <div style="width: 100%; height: 100%; margin:0; padding: 0; ...

  6. vs调试程序缺少 msvcp140d.dll 解决方法

    简介一下吧: 如果只是为了解决问题请直接看第      7       点 ,谢谢. vs2013运行刚安装的opencv问题总结,尤其是电脑还很渣的情况下------花了我起码2天样子----很无奈 ...

  7. Java (四)APACHE Commons IO 复制文件

    上一篇:Java (三)APACHE Commons IO 常规操作 例1:复制文件 1 import java.io.File; 2 import java.io.IOException; 3 4 ...

  8. P2783 有机化学之神偶尔也会作弊 题解

    题面 前言 这道题以前还是道(水)黑题,现在怎么降紫了???? 前置芝士 tarjain 缩点 倍增求LCA或树剖求LCA 脑子... 题意 给你一个无向图,要求你把所有的环缩成一个点.在新得到的图上 ...

  9. 099 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 02 案例分析及实现 03 编写并测试Student类

    099 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 02 案例分析及实现 03 编写并测试Student类 本文知识点:编写并测试Subject类 说明: ...

  10. java的string方法使用

    1.将list转换为","隔开的字符串 //videoIdList值转换成 1,2,3 String videoIds = StringUtils.join(videoIdList ...