• 设计模式(5)——代理模式


    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 中添加一个无参构造函数。

  • 相关阅读:
    LVS-三种负载均衡方式比较
    keepalived和heartbeat区别
    vmware-question
    SQL Server MYSQL 检查点的好处
    MYSQL 引擎的情况
    MYSQL 关闭服务的过程
    SQL Server 行的删除与修改-------------(未完待续P222 deep SQL Server 222 )
    SQL Server一些重要视图 1
    SQL Server 查看数据页面
    SQL Server 向堆表中插入数据的过程
  • 原文地址:https://www.cnblogs.com/linybo/p/13925533.html
Copyright © 2020-2023  润新知