zoukankan      html  css  js  c++  java
  • 设计模式——代理模式

    定义

      为其他对象提供一种代理以控制对这个对象的访问。

      代理模式也叫委托模式,日常生活中很常见。帮别人做某某事情时候,自己就是别人的代理;让别人帮自己做什么事情时候,别人就是自己的代理。

    通用类图

    Subject抽象主题角色

      抽象主题类是一个普通的业务类型的定义,规定要做什么事情。可以是一个抽象类也可以是一个接口。

    RealSubject具体主题角色

      被委托(被代理)的角色,是真正事情处理的执行者。

    Proxy代理主题角色

      委托类(代理类),负责对真实角色的调用,把所有主题限定的方法委托给具体主题角色,也可以在真实主题角色处理前做一些预处理,或在真实主题角色处理后做一些善后处理(比如Spring的AOP)。

    普通代理

    小时候在家都是妈妈做饭,做饭需要洗菜,切菜,炒菜……有时候妈妈有事忙没按时回家,姐姐就偷偷代替妈妈给大家做饭。如果将做饭简单理解为洗菜、切菜、炒菜三个步骤,从程序员的角度来记录做饭这件事就很简单了。妈妈和姐姐都需要洗菜、切菜然后再炒菜,所以可以规定炒菜的三个步骤,然后分别实现这三个步骤,所以可以简单表示为:

    ICook定义了炒菜必须要经历的流程:

    public interface ICook {
        /**洗菜*/
        void washFood();
        /**切菜*/
        void cutFood();
        /**炒菜*/
        void stirFood();
    }

    妈妈(对于一家人来说,天天吃妈妈做的饭,妈妈就是一个Cooker)做饭的流程:

    public class Cooker implements ICook {
        @Override
        public void washFood() {
            System.out.println("妈妈在洗菜……");
        }
    
        @Override
        public void cutFood() {
            System.out.println("妈妈在切菜……");
        }
    
        @Override
        public void stirFood() {
            System.out.println("妈妈在炒菜……");
        }
    }

    一个能代理妈妈做饭(实现了ICook接口)的角色

    public class CookerProxy implements ICook {
        private ICook cooker;
    
        public CookerProxy(ICook cooker){
            this.cooker = cooker;
        }
    
        @Override
        public void washFood() {
            this.cooker.washFood();
        }
    
        @Override
        public void cutFood() {
            this.cooker.cutFood();
        }
    
        @Override
        public void stirFood() {
            this.cooker.stirFood();
        }
    }

    姐姐代替妈妈做饭

        public void test() {
            Cooker cooker = new Cooker();
            ICook cook = new CookerProxy(cooker);
            cook.washFood();
            cook.cutFood();
            cook.stirFood();
        }

    姐姐学会了妈妈的手艺,如果不是亲眼看到,还不知道是姐姐做的饭,这就是真实生活中一个简单的代理例子。

    强制代理

    普通代理是通过代理找到真实的对象,由真实对象去执行动作;但是强制代理却是要通过真实角色查找代理,否则不能访问。也就是不管是通过代理类还是直接new一个真实对象都不能访问,只有通过真实对象委托的代理才能访问。

    还是上面的那个例子,后来妈妈上班去了,中午不能回家做饭了,拿普通代理方式来说,只要是一个会做菜的人,就可以趁着妈妈不在冒充妈妈的身份替妈妈做菜,万一做的很难吃,岂不是把妈妈的招牌给毁了吗?

    于是,妈妈想到了个办法,必须由她指定谁可以代替她做菜,不是由她亲自指定的人无法替她做菜:

    定义了一个getProxy方法,由Cooker来指定自己的代理,并且代理自己做事之前,先验证下身份:

    public class Cooker implements ICook {
        private ICook cook;
    
        @Override
        public void washFood() {
            if (this.isProxy()) {
                System.out.println("妈妈在洗菜……");
            } else {
                System.out.println("请妈妈指定的人来炒菜……");
            }
        }
    
        @Override
        public void cutFood() {
            if (this.isProxy()) {
                System.out.println("妈妈在切菜……");
            } else {
                System.out.println("请妈妈指定的人来炒菜……");
            }
        }
    
        @Override
        public void stirFood() {
            if (this.isProxy()) {
                System.out.println("妈妈在炒菜……");
            } else {
                System.out.println("请妈妈指定的人来炒菜……");
            }
        }
    
        @Override
        public ICook getProxy() {
            this.cook = new CookerProxy(this);
            return this.cook;
        }
    
        private Boolean isProxy() {
            if (this.cook == null) {
                return false;
            }
            return true;
        }
    }

    只有通过Cooker对象获取(getProxy)代理才能做菜。

    代理扩展

    姐姐做了几顿饭之后也开始不乐意了,凭什么我要占用玩的时间来给大家做饭?妈妈想了一个好主意,姐姐每做一顿饭可以从妈妈那里领到1块钱,于是姐姐又高高兴兴地给大家做饭了。做饭和付钱是两个单独的功能,是不能混在一起的,可以将上面类图稍作修改:

    这样,在每次做好饭之后就可以记账应该拿到1元钱

    public class CookerProxy implements ICook, IPay {
        private ICook cooker;
    
        public CookerProxy(ICook cooker){
            this.cooker = cooker;
        }
    
        @Override
        public void washFood() {
            this.cooker.washFood();
        }
    
        @Override
        public void cutFood() {
            this.cooker.cutFood();
        }
    
        @Override
        public void stirFood() {
            this.cooker.stirFood();
            this.pay();
        }
    
        @Override
        public void pay() {
            System.out.println("做完饭拿到了1元钱");
        }
    
        @Override
        public ICook getProxy() {
            /** 可以指定代理的代理,暂时没有就是自己 */
            return this;
        }
    }

    代理不仅可以实现主题的接口,还可以实现其他的接口,在目标行为基础之上做个性化处理。

    动态代理

    后来姐姐长大了,也出门在外了,我们也不能每次都等着吃饭,于是各自都学会了做饭,只要妈妈不在,谁方便了谁就做饭。也就是说,只有饭已经在被做了才知道是谁在做,更灵活了,这就不能提前指定谁来做饭了。对应程序中是:只有在运行时才知道真实类型是谁。

    将类图稍作修改,如下所示。

    InvocationHandler是java提供的动态代理接口,CookerHandler实现了InvocationHandler接口,这样可以动态指定代理,运行时才知道类型。

    public class CookerHandler implements InvocationHandler {
        Class clazz = null;
    
        Object obj = null;
    
        public CookerHandler(Object _obj) {
            this.obj = _obj;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = method.invoke(this.obj, args);
            return result;
        }
    }

    动态代理客户端使用示例:

        public void dynamicProxy(){
            proxy.dynamicproxy.ICook cook = new proxy.dynamicproxy.Cooker();
    
            ClassLoader classLoader = cook.getClass().getClassLoader();
            Class<?>[] interfaces = cook.getClass().getInterfaces();
            CookerHandler handler = new CookerHandler(cook);
    
            proxy.dynamicproxy.ICook proxyInstance = (proxy.dynamicproxy.ICook) Proxy.newProxyInstance(classLoader, interfaces, handler);
            proxyInstance.washFood();
            proxyInstance.cutFood();
            proxyInstance.stirFood();
        }

    参考

    动态代理可以参考:Java动态代理之JDK实现和CGlib实现(简单易懂)

    本文示例代码:Proxy Demo

  • 相关阅读:
    Win32 键盘事件
    好用的Markdown 编辑器及工具
    如何激发您孩子的学习动力和兴趣
    横扫芯片后,紫光欲进军公有云 数百亿资金已到位(大事表)
    C#更改控制台文本颜色
    I/O多路复用
    Python 安装 httpie
    Elasticsearch 5.0
    认证架构
    注册微信小程序
  • 原文地址:https://www.cnblogs.com/mr-yang-localhost/p/9746184.html
Copyright © 2011-2022 走看看