设计模式:使用最广泛的代理模式

news2024/9/17 7:10:35

需求场景

按着惯例,还是以一个应用场景作为代理模式的切入点。现在有一个订单系统,要求是:一旦订单被创建,只有订单的创建人才可以修改订单中的数据,其他人则不能修改。

基本实现思路

按着最直白的思路,就是查询数据库中订单的创建人和当前Session中的登录账号ID是否一致。

class Order {
    private String orderId;
    private String creatorId; // 订单创建者的ID
    private String details; // 订单详情
    // 省略其他属性和getter/setter方法

    public Order(String orderId, String creatorId, String details) {
        this.orderId = orderId;
        this.creatorId = creatorId;
        this.details = details;
    }

    // 其他业务逻辑...
}

系统修改

class OrderService {
    private Map<String, Order> orders = new HashMap<>();

    // 创建订单
    public void createOrder(String orderId, String creatorId, String details) {
        Order order = new Order(orderId, creatorId, details);
        orders.put(orderId, order);
    }

    // 修改订单
    public void modifyOrder(String orderId, String userId, String newDetails) {
        Order order = orders.get(orderId);
        if (order != null) {
            //检查是否拥有权限
            if(order.getCreateId().equals(userId)){
                order.setDetails(newDetails);
            }else{
               System.out.println("权限不足.");
            }
        } else {
            System.out.println("订单不存在.");
        }
    }

    // 其他业务逻辑...
}

该思路的问题

上述代码其实本身是没有问题的,也是Web贫血模式的常见实现思路,即在Service中通过大量的if else进行完成,如果非说问题的话,就是随着对于订单的操作越多Service代码会越发膨胀,例如,需求一开始是只要求改描述,下次又要求更改名称,下下次对于权限又细分等等,Service的modifyOrder就会增加很多的if else和set方法 ,扩展和维护十分的不优雅。或许下面的代理模式能提供一些能够优雅解决的新思路。

代理模式的定义

代理模式的核心定义是:为其他对象提供一种代理以此来控制对这个对象的访问。代理模式是以对象组合的方式对对象进行保护或者说功能扩展的一种方式。

代理模式结构

Sunject :目标接口,定义目标对象的具体操作。

Proxy:代理对象,实现与具体的目标对象一样的接口,这样就可以代理具体的目标对象。保存一个指向具体目标对象的引用,可以再需要的时候调用具体的目标对象,调用目标对象时进行控制和保护。

RealSubject:具体的目标对象,真正实现目标接口要求的功能

代码:

// 定义真实主题角色接口
interface Image {
    void display();
}

// 实现真实主题角色
class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }

    @Override
    public void display() {
        System.out.println("Displaying " + fileName);
    }

    // 模拟从磁盘加载图片资源
    private void loadFromDisk(String fileName) {
        System.out.println("Loading " + fileName + " from disk.");
    }
}

// 定义代理主题角色
interface Proxy extends Image {
    void display();
}

// 实现代理主题角色
class ProxyImage implements Proxy {
    private RealImage realImage;

    public ProxyImage(String fileName) {
        // 延迟加载RealImage对象
        this.realImage = null;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage("image.png");
        }
        realImage.display();
    }
}

public class ProxyPatternDemo {
    public static void main(String[] args) {
        Proxy proxy = new ProxyImage("image.png");
        proxy.display();
    }
}

代理模式实现案例

相当于现在如果有了一个订单对象实例,那么就需要控制外部对它的访问,满足条件的可以访问,不满足条件的就不能访问。

使用代理模式来实现就是需要在Order对象之外再包一层对象,用于操作权限控制。本质上是一种保护代理思路。

 首先创建一个订单的操作接口

public interface OrderApi {
    String getId();
    String getName();
    String getDetails();
    String getCreatorId();
    void setId(String id);
    void setDetails(String details);
    void setName(String name);
    void setCreatorId(String creatorId);
}

 一个基本的订单实体类作为目标代理对象

class Order implements OrderApi {
    private String id;
    private String name;
    private String details;
    private String creatorId;

    public Order(String id, String name, String details, String creatorId) {
        this.id = id;
        this.name = name;
        this.details = details;
        this.creatorId = creatorId;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getDetails() {
        return details;
    }

    @Override
    public String getCreatorId() {
        return creatorId;
    }

    @Override
    public void setId(String id) {
       
        this.id = id;
    }

    @Override
    public void setName(String name) {
        
        this.name = name;
    }

    @Override
    public void setCreatorId(String creatorId) {
        this.creatorId = creatorId;
    }
    
    @Override
    public void setDetails(String details) {
        this.details= details;
    }
}

实现一个代理对象

class OrderProxy implements OrderApi {
    private Order order;

    public OrderProxy(Order order) {
        this.order = order;
    }

    @Override
    public String getId() {
        return order.getIdO();
    }

    @Override
    public String getName() {
        return order.getNameO();
    }

    @Override
    public String getDetails() {
        return order.getDetailsO();
    }

    @Override
    public String getCreatorId() {
        return order.getCreatorIdO();
    }

    @Override
    public void setId(String id) {
        // 在这里添加权限检查逻辑
        if (isCreator()) {
            order.setId(id);
        } else {
            throw new SecurityException("Only the creator can change the order ID.");
        }
    }

    @Override
    public void setName(String name) {
        // 在这里添加权限检查逻辑
        if (isCreator()) {
            order.setName(name);
        } else {
            throw new SecurityException("Only the creator can change the order name.");
        }
    }

    @Override
    public void setCreatorId(String creatorId) {
        // 创建者ID通常不允许更改
        throw new UnsupportedOperationException("Changing creator ID is not allowed.");
    }

    private boolean isCreator(String userId) {
        // 这里应该添加检查userId是否是订单的创建者
        // 为了示例简单,这里假设userId总是传入正确的,返回true
        return true;
    }
}

代理模式的理解

特点与分类

代理模式的本质是控制对象的访问。通过代理目标对象,把代理对象插入到客户和目标对象之间,从而为客户和目标对象引入一定的间接性。正是这个间接性,给了代理对象很多的活动空间。代理对象可以在调用具体的目标对象前后,附加很多操作,从而实现新的功能或是扩展目标对象的功能。更狠的是,代理对象还可以不去创建和调用目标对象,也就是说,目标对象被完全代理掉了,或是被替换掉了。
从实现上看,代理模式主要是使用对象的组合和委托,尤其是在静态代理的实现里面,会看得更清楚。但是也可以采用对象继承的方式来实现代理。这种实现方式在某些情况下,会比使用组合来的简单。还是以上面权限的例子,以继承的方式实现,首先剔除掉OrderApi

public class Order {
  //仅仅去除OrderApi的实现,其他的代码没有任何变化,就不再赘述了
}

代理对象:

public class OrderProxy extends Order {
    public OrderProxy(String productName, int orderNum, String orderUser) {
        super(productName, orderNum, orderUser);
    }

    public void setProductName(String productName, String user) {
        // 控制访问权限,只有创建订单的人员才能够修改
        if (user == null || user.equals(this.getOrderUser())) {
            super.setProductName(productName, user);
        } else {
            System.out.println("对不起, " + user + ",您无权修改订单中的产品名称。");
        }
    }

    public void setOrderNum(int orderNum, String user) {
        // 控制访问权限,只有创建订单的人员才能够修改
        if (user == null || user.equals(this.getOrderUser())) {
            super.setOrderNum(orderNum, user);
        } else {
            System.out.println("对不起, " + user + ",您无权修改订单中的订购数量。");
        }
    }

    public void setOrderUser(String orderUser, String user) {
        // 控制访问权限,只有创建订单的人员才能够修改
        if (user == null || user.equals(this.getOrderUser())) {
            super.setOrderUser(orderUser, user);
        } else {
            System.out.println("对不起, " + user + ",您无权修改订单中的订购人。");
        }
    }

}

不同的代理类型,这种附加的间接性有不同的用途,也就具有不同的特点。

  • 远程代理:隐藏了一个对象存在于不同的地址空间的事实,也即是客户通过远程代理去访问一个对象,根本就不关心这个对象在哪里,也不关心如何通过网络去访问到这个对象。从客户的角度来讲,它只是在使用代理对象而已。
  • 虚代理:可以根据需要来创建“大”对象,只有到必须创建对象的时候,虚代理才会创建对象,从而大大加快程序运行速度,并节省资源。通过虚代理可以对系统进行优化。
  • 保护代理:可以在访问一个对象的前后,执行很多附加的操作,除了进行权限控制之外,还可以进行很多跟业务相关的处理,而不需要修改被代理的对象。也就是说,可以通过代理来给目标对象增加功能。
  • 智能指引:和保护代理类似,也是允许在访问一个对象的前后,执行很多附加的操作,这样一来就可以做很多额外的事情,比如,引用计数等。

在这些代理类型中,最常见的是保护代理远程代理。上述的例子就是一个典型的保护代理的实现,即具体订单的操作是不变的,如果需要对订单的操作进行特殊处理,一切变动皆集中在代理对象中,代理对象对于订单对象起到了保护隔离的作用,同时代码层面上也承载了“频繁变化”的需求内容,将“变化”隔离出来,对于后续的需求扩展也是十分有效。

建议在如下情况中选用代理模式。

  • 需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理
  • 需要按照需要创建开销很大的对象的时候,可以使用虚代理
  • 需要控制对原始对象的访问的时候,可以使用保护代理
  • 需要在访问对象执行一些附加操作的时候,可以使用智能指引代理

具体目标和代理间的关系

Java中代理模式的应用

Java 对代理模式提供了内建的支持,在java.lang,refect 包下面,提供了一个 Proxy的类和一个InvocationHandler 的接口。
通常把前面自己实现的代理模式称为 Java 的静态代理。这种实现方式有一个较大的缺点,就是如果Subject接口发生变化,那么代理类和具体的目标实现都要变化,不是很灵活。而使用Java内建的对代理模式支持的功能来实现则没有这个问题。
通常把使用 Java 内建的对代理模式支持的功能来实现的代理称为Java的动态代理,动态代理跟静态代理相比,明显的变化是:静态代理实现的时候,在Subject接口上定义很多的方法,代理类里面自然也要实现很多方法:而动态代理实现的时候,虽然Subicct接口上定义了很多方法,但是动态代理类始终只有一个invoke 方法。这样,当Subject接口发生变化的时候,动态代理的接口就不需要跟着变化了。
Java的动态代理目前只能代理接口,基本的实现是依靠Java的反射机制和动态生成class的技术,来动态生成被代理的接口的实现对象。具体的内部实现细节这里不去讨论。如果要实现类的代理,可以使用cglib(一个开源的Code Generation Library)。

还是来看看示例,那就修改上面保护代理的示例,看看如何使用Java的动态代理来实现同样的功能。
(1)订单接口的定义是完全一样的,就不再赘述了。
(2)订单对象的实现,只是添加了一个 toString,,以方便测试输出,这里也不去示例了。在前面的示例中,toString已实现在代理类里面了。
(3)直接看看代理类的实现,大致有如下变化。

  • 要实现InvocationHandler接口。
  • 需要提供一个方法来实现:把具体的目标对象和动态代理绑定起来,并在绑定好过后,返回被代理的目标对象的接口,以利于客户端的操作。
  • 需要实现 invoke 方法,在这个方法里面,具体判断当前是在调用什么方法,需要如何处理。
import java.lang.reflect.*;

/**
 * 使用Java中的动态代理
 */
public class DynamicProxy implements InvocationHandler {
    // 被代理的对象
    private OrderApi order;

    /**
     * 获取绑定好代理和具体目标对象后的目标对象的接口
     * @param order 具体的订单对象,相当于具体目标对象
     * @return 绑定好代理和具体目标对象后的目标对象的接口
     */
    public OrderApi getProxyInterface(Order order) {
        // 设置被代理的对象,方便invoke里面的操作
        this.order = order;

        // 把真正的订单对象和动态代理关联起来
        OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(
            order.getClass().getClassLoader(),
            order.getClass().getInterfaces(),
            this);

        return orderApi;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 如果是调用setter方法就需要检查权限
        if (method.getName().startsWith("set")) {
            // 假设Order类有一个getOrderUser()方法来获取订单创建者的用户ID
            // 并且order.getOrderUser().equals(args[0])来检查是否是创建者
            if (order.getOrderUser() != null && order.getOrderUser().equals(args[0])) {
                // 可以操作
                return method.invoke(order, args);
            } else {
                // 如果不是创建者,不能修改
                System.out.println("对不起," + args[0] + ",您无权修改本订单中的数据");
            }
        } else {
            // 不是调用的setter方法就继续运行
            return method.invoke(order, args);
        }
        return null;
    }
}

使用时则是:

public class Client {
    public static void main(String[] args) {
        // 张三先登录系统创建了一个订单
        Order order = new Order("XXX", 100, "张三");
        // 创建一个动态代理
        DynamicProxy dynamicProxy = new DynamicProxy();
        // 然后把订单和动态代理关联起来
        OrderApi orderApi = dynamicProxy.getProxyInterface(order);

        // 以下就需要使用被代理过的接口来操作了
        // 李四想要来修改,那就会报错
        orderApi.setOrderNum(123, "李四");
        // 输出order
        System.out.println("李四修改后订单记录没有变化:" + orderApi);

        // 张三修改就不会有问题
        orderApi.setOrderNum(123, "张三");
        // 再次输出order
        System.out.println("张三修改后,订单记录:" + orderApi);
    }
}

代理在Java中的使用十分常见,例如Spring中的AOP,其本质就是代理模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1935310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构小测试:排序算法

目录 1、请简述数据结构八大排序算法的思路。 2、常用排序算法手写 冒泡排序&#xff1a; 选择排序&#xff1a; 快速排序&#xff1a; 归并排序&#xff1a; 堆排序&#xff1a; 3、额外再加一个二分查找吧 1、请简述数据结构八大排序算法的思路。 冒泡排序&#xff…

golang开发环境搭建与踩坑记录

文章目录 一、安装下载1、go环境2、ide 二、基本使用1、运行2、结构体与方法函数指针3、闭包4、指针5、map6、接口7、异常 三、包管理1、go mod语法2、项目下载所有依赖 一、安装下载 1、go环境 下载地址&#xff1a;https://go.dev/dl/ 或者&#xff1a;https://golang.goog…

19.x86游戏实战-创建MFC动态链接库

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

向量数据库选择浅谈

初见大模型 作为新手接触大模型后&#xff0c;LLM模型、Embedding模型、rank模型、vector模型等等选择上可谓是一步一个坑&#xff0c;迷茫的走在迷茫的大路上。总之各种途径去选择合适的模型&#xff0c;今天了解下向量数据库选择依据。 借鉴前人 学习之前先看看大家都在关注…

基于java+springboot+vue实现的中小企业人事管理系统(文末源码+Lw)128

基于SpringBootVue的实现的中小企业人事管理系统&#xff08;源码数据库万字Lun文流程图ER图结构图ppt演示视频软件包&#xff09; 系统角色&#xff1a; 员工、管理员 系统功能&#xff1a; 管理员登录 进入中小企业人事管理系统可以查看首页、个人中心、员工管理、部门信息管…

arthas监控本地耗时代码(windows)

1、安装 curl -O https://arthas.aliyun.com/arthas-boot.jar 2、运行 java -jar arthas-boot.jar 3、选择监控的程序端口 运行后如下&#xff1a;第二个是我的后端程序&#xff0c;我选择2后回车 4、监控代码块 trace com.example.demo.service.impl.LoginServiceImp…

Docker 镜像使用和安装

​ 1、简介 Docker是一个开源的应用容器引擎&#xff1b;是一个轻量级容器技术&#xff1b; Docker支持将软件编译成一个镜像&#xff1b;然后在镜像中各种软件做好配置&#xff0c;将镜像发布出去&#xff0c;其他使用者可以直接使用这个镜像&#xff1b; 运行中的这个镜像…

Java面试八股之Redis怎么实现消息队列

Redis怎么实现消息队列 Redis实现消息队列主要依赖于其内置的数据结构&#xff0c;如List、Pub/Sub&#xff08;发布/订阅&#xff09;和Stream。下面将分别介绍这三种方式及其特点&#xff1a; 1. List实现消息队列 Redis的List是一个双向链表&#xff0c;支持快速的头部和…

【学术会议征稿】第六届信息与计算机前沿技术国际学术会议(ICFTIC 2024)

第六届信息与计算机前沿技术国际学术会议(ICFTIC 2024) 2024 6th International Conference on Frontier Technologies of Information and Computer 第六届信息与计算机前沿技术国际学术会议(ICFTIC 2024)将在中国青岛举行&#xff0c;会期是2024年11月8-10日&#xff0c;为…

Python面试宝典第15题:岛屿数量

题目 在二维网格地图上&#xff0c;1 表示陆地&#xff0c;0 表示水域。如果相邻的陆地可以水平或垂直连接&#xff0c;则它们属于同一块岛屿。请进行编码&#xff0c;统计地图上的岛屿数量。比如&#xff1a;下面的二维网格地图&#xff0c;其岛屿数量为3。 基础知识 解决这类…

Linux入门以及Linux文件编程学习

Linux学习必备 首先我们学习Linux必须安装一个虚拟机&#xff0c;我是跟着韦东山老师安装的&#xff0c;具体可以跟着视频操作&#xff0c;简单易懂&#xff1a;安装虚拟机 Linux入门最基本简单的指令 一、Vi的使用 Vi文件名 创建或者打开一个文件&#xff0c;进入默认命令行…

[论文笔记] pai-megatron-patch Qwen2-CT 长文本rope改yarn

更改: # Copyright (c) 2024 Alibaba PAI and Nvidia Megatron-LM Team. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License a…

Linux——多路复用之select

目录 前言 一、select的认识 二、select的接口 三、select的使用 四、select的优缺点 前言 在前面&#xff0c;我们学习了五种IO模型&#xff0c;对IO有了基本的认识&#xff0c;知道了select效率很高&#xff0c;可以等待多个文件描述符&#xff0c;那他是如何等待的呢&a…

JavaScript 获取 url(get)参数

https://andi.cn/page/621584.html

Gitee 使用教程1-SSH 公钥设置

一、生成 SSH 公钥 1、打开终端&#xff08;Windows PowerShell 或 Git Bash&#xff09;&#xff0c;通过命令 ssh-keygen 生成 SSH Key&#xff1a; ssh-keygen -t ed25519 -C "Gitee SSH Key" 随后摁三次回车键&#xff08;Enter&#xff09; 2、查看生成的 SSH…

大鲸鱼docker-compose单机容器集群编排工具

目录 一、Docker-compose 概述 二、Docker-compose简介 三、YML文件格式及编写注意事项 1.yml文件是什么 2.yml问价使用注意事项 3.yml文件的基本数据结构 四、Docker-compose 配置 1.Docker-Compose 配置常用字段 2.Docker Compose常用命令 3.使用Docker-compose创建…

Python解释器:CPython 解释器

一、什么是python解释器 Python解释器是一种用于执行Python代码的程序。 它将Python源代码转换为机器语言或字节码&#xff0c;从而使计算机能够执行。 1.1 Python解释器分类 1、CPython CPython 是 Python 的主要实现&#xff0c;由 C 语言编写。大多数用户在日常开发中使…

django实现用户的注册、登录、注销功能

创建django项目的步骤&#xff1a;Django项目的创建步骤-CSDN博客 一、前置工作 配置数据库&#xff0c;设置数据库引擎为mysql 1、在settings文件中找到DATABASES, 配置以下内容 DATABASES {"default": {ENGINE: django.db.backends.mysql, # 数据库引擎NAME: dja…

色彩与故乡的对话 —— 钱华个人油画展正式开展

色彩与故乡的对话 —— 钱华个人油画展正式开展 2024年7月17日 &#xff0c;在宁波这座历史与现代交织的城市里&#xff0c;艺术与文化的碰撞再次绽放出耀眼的光芒。由宁波海曙区美术家协会主办&#xff0c;宁波市海纳广场开发经营有限公司协办的“色彩与故乡的对话——钱华个人…

【SpringBoot Web开发之静态资源访问】笔记

详细内容见官方文档&#xff1a;Static Content SpringBoot Web开发之静态资源访问 1.准备工作&#xff1a;创建WebDemo2.静态资源目录2.1官网原文2.2静态资源目录第一步&#xff1a;依照上面2.1官网原文中创建如下目录第二步&#xff1a;复制粘贴图片到静态资源目录中第三步…