写点什么

一个江南皮鞋厂的小故事带我理解透了——什么是“代理模式”

发布于: 2021 年 05 月 06 日
一个江南皮鞋厂的小故事带我理解透了——什么是“代理模式”

引子

小帅前几年开了一家鞋厂,专门生产男鞋,做批发生意,刚开始生意还不错,日子过得蛮滋润的。

不料,好景不长,眼看这两年订单量直线下降,生产线上的工人看赚不到钱,于是纷纷离职,只剩下几个老工友,偶尔有个稍微大一点的单子竟也接不动了。

这样下去就只能关门大吉了,小帅不得不另寻出路。前几日业内好朋友聚会,好友刘望星的工厂生意甚是红火,让小帅羡慕不已。

小帅心想,为何不把自己的订单直接给刘望星代工生产呢?把自己的工厂干脆关了,就负责接单,做个代理商得了!

小帅和刘望星一拍即合,合作正式开始了。

小帅的任务无非就是接单,发货,为了不让客户知道自己只是个代理商,小帅特意让工厂先把货发到自己这里,贴上自己的牌子,然后自己再把货发给客户,让客户以为鞋子都是自己的工厂生产的。

代理模式

整个过程用代码展示如下:

/** * 鞋厂接口 */public interface IShoesFactory {
    /**     * 参观工厂     */    void visitFactory();
    /**     * 下订单     * @param price 金额     */    void placeOrder(double price);
    /**     * 发货     */    void ship();}

复制代码


点击并拖拽以移动


/** * 鞋厂类 */public class ShoesFactory implements IShoesFactory{
    private String name;
    public ShoesFactory(String name) {        this.name = name;    }
    /**     * 参观工厂     */    @Override    public void visitFactory() {        System.out.println("带客户参观" + name);    }
    /**     * 下订单     *     * @param price 金额     */    @Override    public void placeOrder(double price) {        System.out.println("接到" + price + "元的订单");    }
    /**     * 发货     */    @Override    public void ship() {        System.out.println("开始发货");    }}
复制代码


点击并拖拽以移动


/** * 代理鞋厂类 */public class ShoesFactoryProxy implements IShoesFactory{
    private IShoesFactory shoesFactory;
    public ShoesFactoryProxy(IShoesFactory shoesFactory) {        this.shoesFactory = shoesFactory;    }
    /**     * 参观工厂     */    @Override    public void visitFactory() {        shoesFactory.visitFactory();    }
    /**     * 下订单     *     * @param price 金额     */    @Override    public void placeOrder(double price) {        shoesFactory.placeOrder(price);    }
    /**     * 发货     */    @Override    public void ship() {        shoesFactory.ship();    }}
复制代码


点击并拖拽以移动

客户类:

public class Client {    public static void main(String[] args) {        IShoesFactory factory = new ShoesFactory("刘望星的工厂");        IShoesFactory factoryProxy = new ShoesFactoryProxy(factory);        // 参观工厂        factoryProxy.visitFactory();        // 下订单        factoryProxy.placeOrder(10000);        // 发货        factoryProxy.ship();    }}

复制代码


点击并拖拽以移动

输出结果:

带客户参观刘望星的工厂接到10000.0元的订单开始发货
复制代码


点击并拖拽以移动

客户要参观工厂,小帅就带客户去参观刘望星的工厂,和客户说这就是自己的工厂。客户向小帅下单,小帅就把订单下给刘望星生产。

客户类调用代理的方法,代理类再调用业务类的方法完成工作。

代理模式定义

代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。 代理模式的英语叫 Proxy,它是一种结构型模式。



点击并拖拽以移动


代理背后一般至少有一个实际对象,代理的外部功能和实际对象一般是一样的,用户与代理打交道,不直接接触实际对象,甚至不知道实际对象的存在。

代理模式到底有什么用呢?难道仅仅是通过代理类做个转发吗?

当然不是的,虽然外部功能和实际对象一样,但代理有它存在的价值,代理也分为多种类型比如:

  • 远程(Remote)代理:适用于调用远程服务器对象的情况,代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节,简化了客户端的调用。

  • 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

  • Copy-on-Write 代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个 开销较大的操作,Copy-on-Write 代理可以让这个操作延迟,只有对象被用到的时候才被克隆。

  • 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

  • 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

  • 防火墙(Firewall)代理:保护目标不让恶意用户接近。

  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。

  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

我们来看看几个例子:

保护代理

保护(Protect or Access)代理的作用是,控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

如果我们不想客户访问某个功能,就可以在代理类里做手脚,毕竟业务类都是通过代理类调用的嘛。

比如,小帅怕露馅,不想让客户参观工厂,就可以改写参观工厂的方法:



点击并拖拽以移动


输出:

工厂禁止参观接到10000.0元的订单开始发货
复制代码


点击并拖拽以移动

智能引用代理

智能引用(Smart Reference)代理的作用是,当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

比如,小帅作为代理商,肯定是要赚钱的,客户下了一个 10000 元的订单,小帅就和刘望星谈好提成 10%,就可以在代理类中把金额扣掉。

还有,小帅的工厂有自己的品牌,发货之前还要贴上自己的牌子。



点击并拖拽以移动


输出:

工厂禁止参观接到9000.0元的订单贴上小帅牌标签。开始发货
复制代码


点击并拖拽以移动

代理模式的几种应用

  • 图片代理:一个很常见的代理模式的应用实例就是对大图浏览的控制。用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理。


    在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。



    如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。

  • 远程代理:远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的远程业务对象是局域的而不是远程的,而远程代理对象承担了大部分的网络通信工作。

  • 虚拟代理:当一个对象的加载十分耗费资源的时候,虚拟代理的优势就非常明显地体现出来了。虚拟代理模式是一种内存节省技术,那些占用大量内存或处理复杂的对象将推迟到使用它的时候才创建。

动态代理

上面说的这些方法是静态代理模式,真实的业务类必须是事先已经创建好的的,并把它传给代理对象,作为一个内部成员。

静态代理是这样子的:



点击并拖拽以移动



点击并拖拽以移动


如果一个真实业务类必须对应一个代理类,这将导致系统中的代理类的个数急剧增加,比如有 10 个不同的业务类,那么必须要有 10 个对应的代理类,因此需要想办法减少系统中类的个数。

动态代理可以在事先不知道真实业务类的情况下使用代理类,在程序运行期间由 JVM 根据反射等机制动态的生成,动态代理的典型应用就是 Spring AOP。

下面我们看一下 Java SDK 动态代理的例子:

/** * 鞋厂接口 */public interface IShoesFactory {
    /**     * 参观工厂     */    void visitFactory();
    /**     * 下订单     * @param price 金额     */    void placeOrder(double price);
    /**     * 发货     */    void ship();}

复制代码


点击并拖拽以移动


/** * 鞋厂类 */public class ShoesFactory implements IShoesFactory {
    private String name;
    public ShoesFactory(String name) {        this.name = name;    }
    /**     * 参观工厂     */    @Override    public void visitFactory() {        System.out.println("带客户参观" + name);    }
    /**     * 下订单     *     * @param price 金额     */    @Override    public void placeOrder(double price) {        System.out.println("接到" + price + "元的订单");    }
    /**     * 发货     */    @Override    public void ship() {        System.out.println("开始发货");    }}
复制代码


点击并拖拽以移动

InvocationHandler 接口是 proxy 代理实例的调用处理程序实现的一个接口,代理类每调用一次方法就会进入这里的 invoke 方法。

public class ShoesFactoryHandler implements InvocationHandler {
    /**     * 被代理的对象,实际的方法执行者     */    private Object proxiedObject;
    public ShoesFactoryHandler(Object proxiedObject) {        this.proxiedObject = proxiedObject;    }
    /**     * 代理类每调用一次方法就会进入这里     * @param proxy 表示代理对象本身,需要注意,它不是被代理的对象     * @param method 表示正在被调用的方法     * @param args 表示方法的参数     * @return     * @throws Throwable     */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        /*if(method.getName().equals("visitFactory")) {            System.out.println("工厂禁止参观");        }*/        if(method.getName().equals("placeOrder")) {            // 如果是下订单方法,给代工厂9折的价格            method.invoke(proxiedObject, Double.parseDouble(String.valueOf(args[0])) * 0.9);        }        else if(method.getName().equals("ship")) {            // 如果是发货方法,发货之前先贴上标签            System.out.println("贴上小帅牌标签");            method.invoke(proxiedObject, args);        } else {            // 其他方法,直接调用            method.invoke(proxiedObject, args);        }        return null;    }}
复制代码


点击并拖拽以移动

Proxy 类调用 newProxyInstance 方法来创建一个代理对象,上面的 InvocationHandler 对象作为参数之一。

public class ShoesFactoryProxy {    /**     * 生成动态代理     * @param proxiedObject 被代理的对象,实际的方法执行者     * @return     */    public Object createProxy(Object proxiedObject) {        // 获取对应的ClassLoader        ClassLoader classLoader = proxiedObject.getClass().getClassLoader();        // 获取所有的接口        Class[] interfaces = proxiedObject.getClass().getInterfaces();        // 创建一个传给代理类的调用请求处理器        ShoesFactoryHandler handler = new ShoesFactoryHandler(proxiedObject);        return Proxy.newProxyInstance(classLoader, interfaces, handler);    }}
复制代码


点击并拖拽以移动

客户端类:

public class Client {    public static void main(String[] args) {        IShoesFactory factory = new ShoesFactory("刘望星的工厂");        ShoesFactoryProxy shoesFactoryProxy = new ShoesFactoryProxy();        IShoesFactory factoryProxy = (IShoesFactory)shoesFactoryProxy.createProxy(factory);        // 参观工厂        factoryProxy.visitFactory();        // 下订单        factoryProxy.placeOrder(10000);        // 发货        factoryProxy.ship();    }}

复制代码


点击并拖拽以移动

输出结果:

带客户参观刘望星的工厂接到9000.0元的订单贴上小帅牌标签开始发货
复制代码


点击并拖拽以移动

通用性更强些的动态代理类一般是这样的:

public class NormalHandler implements InvocationHandler {
    /**     * 被代理的对象,实际的方法执行者     */    private Object proxiedObject;
    public NormalHandler(Object proxiedObject) {        this.proxiedObject = proxiedObject;    }
    /**     * 代理类每调用一次方法就会进入这里     * @param proxy 表示代理对象本身,需要注意,它不是被代理的对象     * @param method 表示正在被调用的方法     * @param args 表示方法的参数     * @return     * @throws Throwable     */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {           // 调用实际方法之前的操作        preRequest();        // 调用实际方法           Object result = method.invoke(proxiedObject, args);           // 调用实际方法之后的操作           afterRequest();        }        return result;    }}
复制代码


点击并拖拽以移动

这样,我们就能在 preRequest()和 afterRequest()方法中做很多事情了,比如记录日志,记录调用时间等等。

总结

代理模式看上去和装饰者模式很像,不过它们的意图是不一样的,装饰者模式是为对象加上新的行为,而代理模式是作为真实对象的替身,控制对象的访问。

比如,保护代理,进行权限控制,不让客户访问某些功能;比如,虚拟代理,在真实的对象创建之前,先返回预定设定好的信息。

下面我们来看看代理模式的优点和缺点:

优点

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。

  • 你可以在客户端毫无察觉的情况下控制服务对象。

  • 即使服务对象还未准备好或不存在, 代理也可以正常工作。

  • 符合开闭原则, 你可以在不对服务或客户端做出修改的情况下创建新代理。

缺点

  • 由于在客户端和真实业务类之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。

  • 实现代理模式需要额外的工作,增加了程序的复杂度。

原文:https://mp.weixin.qq.com/s/O_MGV_XHgXIZuQYW81MB4Q

推荐阅读

为什么阿里巴巴的程序员成长速度这么快

进大厂也就这回事,工作2到3年后进大厂操作指南

23岁拿到阿里60w年薪,他是怎么做到的?

爆赞!2021年阿里P9纯手打十亿级高并发系统手册,开源分享


看完三件事

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。

关注公众号 『 Java 斗帝 』,不定期分享原创知识。

同时可以期待后续文章 ing🚀

用户头像

还未添加个人签名 2020.09.07 加入

还未添加个人简介

评论

发布
暂无评论
一个江南皮鞋厂的小故事带我理解透了——什么是“代理模式”