• 【设计模式】26.结构型模式-代理模式(Proxy)


    一、描述

    定义:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。代理模式分为静态代理、动态代理、CGLIB代理等方式,稍后会举例说明。

    角色:

    1.抽象对象:一般是来声明代理对象和被代理对象之间的公共方法,该类可以是接口,也可以是抽象类。
    2.目标对象:又称为被代理类,是真正执行业务逻辑的类
    3.代理对象:又称为代理类,此类中一般包含了目标对象的引用,因此具备了目标对象的代理权。并且可以对目标对象进行逻辑增强或控制性操作。

    类图:

    在这里插入图片描述
     从定义上来看,其实和中介者模式(行为模式)有相似之处,但是具体区分的话,其实在目的,角色划分上是不同的,
    1.目的:中介者模式是为了解耦,将访问者与被访问者解耦,避免直接关联;代理模式是为了控制对某对象的访问。
    2.角色:中介者模式分为四种角色:抽象中介类、具体中介类、抽象同事类、具体同事类;代理模式分为三种角色:抽象对象、目标对象、代理对象

    二、优点

    1.代理模式能将代理对象和目标对象分离
    2.在一定程度上降低了系统的耦合,扩展性好
    3.可以起到保护目标的作用
    4.可以起到增强目标对象的作用

    三、缺点

    1.请求会先通过代理类,会降低请求效率
    2.系统复杂度增加

    四、适用场景

    最常用的场景便是Spring 的AOP了,相信很多同学都用过。

    五、示例

    以“点外卖为例”,小王想点一份25块钱西红柿盖浇饭,但是不想出门,于是在外卖平台上点了某个饭店外卖,外卖平台上有满减,满20-2,于是小王只花了23块钱就得到了一份盖浇饭。
      分析一下角色分类:
      客户:小王;代理类:外卖平台;委托类:饭店
      依次举例不同代理分类的代码:

    1.静态代理

    (1)抽象下单类
    public interface AbstractOrder {
        /**
         * 下单
         *
         * @return
         */
        Double placeOrder(Integer type);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    (2)菜单枚举
    @Getter
    @AllArgsConstructor
    public enum OrderList {
        XI_HONG_SHI_CHAO_DAN(1, "西红柿炒鸡蛋盖饭",new Double(25)),
        LA_JIAO_CHAO_ROU(2, "辣椒炒肉盖饭",new Double(20)),
        MU_XU_ROU(3, "木须肉肉盖饭",new Double(18));
    
        /**
         * 菜单类型
         */
        private Integer type;
        /**
         * 菜单名称
         */
        private String name;
        /**
         * 价格
         */
        private Double price;
    
    
        private static final Map<Integer, OrderList> MAP = Stream.of(OrderList.values())
                .collect(Collectors.toMap(OrderList::getType, Function.identity()));
    
        public static OrderList get(Integer type) {
            return MAP.get(type);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    (3)小王下单类
    public class XiaoWangOrder implements AbstractOrder {
        @Override
        public Double placeOrder(Integer type) {
            OrderList orderList = OrderList.get(type);
            System.out.println("小王:我想点一份" + orderList.getName() + "(" + orderList.getPrice() + ")");
            return orderList.getPrice();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    (4)外卖平台
    public class DeliveryPlatform implements AbstractOrder {
        //小王下单
        private XiaoWangOrder xiaoWangOrder;
        
        public DeliveryPlatform(XiaoWangOrder xiaoWangOrder) {
            this.xiaoWangOrder = xiaoWangOrder;
        }
    
        @Override
        public Double placeOrder(Integer type) {
            System.out.println("使用外卖平台点单:");
            Double money = xiaoWangOrder.placeOrder(type);
            //判断价格是否大于20元,若大于,优惠2元
            if (Double.compare(money, new Double(20)) > 0) {
                System.out.println("外卖平台已满20元,优惠2元");
                return money - 2;
            }
            return money;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    (5)测试
    public class Client {
        public static void main(String[] args) {
            //将小王初始化到外卖平台,小王可以点单
            DeliveryPlatform platform = new DeliveryPlatform(new XiaoWangOrder());
            Double money = platform.placeOrder(OrderList.XI_HONG_SHI_CHAO_DAN.getType());
            System.out.println("最终消费金额:" + money);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    测试结果:
    在这里插入图片描述

    2.动态代理

    抽象类、小王、枚举类不变,变化的是代理类,使用InvocationHandler与Proxy代理类处理,注意包全是java.lang.reflect下面的类。

    (1)代理类
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * DeliveryPlatform
     *
     * @author zhouxy
     * @date 2022/8/5
     **/
    public class DeliveryPlatform implements InvocationHandler {
        private AbstractOrder abstractOrder;
    
        /**
         * 获取实例对象
         *
         * @param abstractOrder
         * @return
         */
        public AbstractOrder getInstance(AbstractOrder abstractOrder) {
            this.abstractOrder = abstractOrder;
            Class<?> clazz = abstractOrder.getClass();
            //将当前代理类加入到处理下单类的逻辑中,当有访问该abstractOrder的时候,会被invoke方法拦截处理。
            return (AbstractOrder) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            before();
            Object obj = method.invoke(abstractOrder, args);
            //根据返回结果处理数据
            return after((Double) obj);
        }
    
        public void before() {
            System.out.println("外卖平台开始点单");
        }
    
        public Double after(Double price) {
            //优惠金额大于20时,则优惠2元
            if (Double.compare(price, 20) > 0) {
                System.out.println("消费金额大于20,优惠2元");
                price = price - 2;
            }
            System.out.println("外卖平台点单完成");
            return price;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    其中,着重解释一下Proxy类,此类为代理类,使用到的为newProxyInstance方法,该方法的作用是:

    Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
    
    
    百度翻译:返回指定接口的代理类实例,该接口将方法调用分派给指定的调用处理程序。
    
    • 1
    • 2
    • 3
    • 4

    根据注释可以很清楚的看出该方法的用处,即将指定的调用程序DeliveryPlatform指定给getInstance方法中的abstractOder类中的方法,在访问该类时,会走DeliveryPlatform中的invoke方法。

    (2)测试
    public class Client {
        public static void main(String[] args) {
            DeliveryPlatform deliveryPlatform = new DeliveryPlatform();
            AbstractOrder xiaoWangOrder = deliveryPlatform.getInstance(new XiaoWangOrder());
            Double price = xiaoWangOrder.placeOrder(OrderList.XI_HONG_SHI_CHAO_DAN.getType());
            System.out.println("最终消费:" + price);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    测试结果:
     在这里插入图片描述

    3.CGLIB

    (1)CGLIB实现代理类
    public class DeliveryPlatformCGLIB implements MethodInterceptor {
    
        /**
         * 获取实例,是一个字节码增强器,可以用来为委托类创建代理
         *
         * @param clazz
         * @return
         */
        public Object getInstance(Class<?> clazz) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            before();
            //处理委托类方法
            Object obj = methodProxy.invokeSuper(o, objects);
            return after((Double) obj);
        }
    
        public void before() {
            System.out.println("外卖平台开始点单");
        }
    
        /**
         * 优惠
         *
         * @param price
         * @return
         */
        public Double after(Double price) {
            //优惠金额大于20时,则优惠2元
            if (Double.compare(price, 20) > 0) {
                System.out.println("消费金额大于20,优惠2元");
                price = price - 2;
            }
            System.out.println("外卖平台点单完成");
            return price;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    (2)测试
    public class Client {
        public static void main(String[] args) {
            DeliveryPlatformCGLIB cglib = new DeliveryPlatformCGLIB();
            XiaoWangOrder xiaoWangOrder = (XiaoWangOrder) cglib.getInstance(XiaoWangOrder.class);
            Double price = xiaoWangOrder.placeOrder(OrderList.XI_HONG_SHI_CHAO_DAN.getType());
            System.out.println("最终消费:" + price);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    测试结果:
    在这里插入图片描述

  • 相关阅读:
    垂起固定翼无人机基础知识,垂起固定翼无人机应用前景,垂起固定翼无人机优缺点分析
    【洛谷】P2082 区间覆盖(加强版)
    IntelliJ IDEA 2022.2 正式来临:已完全支持 Spring 6 和 Spring Boot 3
    Kubernetes Pod内存监控
    延时中间继电器HJZS-93/220VDC
    golang工程——opentelemetry简介、架构、概念、追踪原理
    从0到1,申请cos服务器并上传图片到cos文件服务器
    密集计算场景下的 JNI 实战
    开题:轴承的剩余寿命预测(为什么要长时间长序列预测,意义)
    SSM学习——apipost测试几种常用的请求与响应(11)
  • 原文地址:https://blog.csdn.net/CAT_cwds/article/details/126725191