代理模式-对象的间接访问

news2024/11/18 14:40:11

 现在朋友圈有好多做香港代购的微商,大部分网民无法自己去香港购买想要的商品,于是委托这些微商,告诉他们想要的商品,让他们帮我们购买。我们只需要付钱给他们,他们就会去香港购买,然后把商品寄给我们。这就是一种代理模式。

1 代理模式概述

引入一个新的代理对象,在客户端对象和目标对象之间起到中介的作用,去掉客户不能看到的内容和服务或者增添客户想要的额外服务。

图 代理模式结构图

  1. Subject:抽象主题角色。声明了真实主题和代理主题的共同接口,使得在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题进行编程。
  2. Proxy:代理主题角色。包含了对真实主题的引入,从而可以在任何时候操作真实主题对象。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅是单纯调用真实主题对象中的操作。
  3. RealSubject:真实主题角色。实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
public interface Subject {

    void buy(String goodsName);

}

public class RealSubject implements Subject{

    @Override
    public void buy(String goodsName) {
        System.out.println("购买:" + goodsName);
    }

}

public class ProxyBuy implements Subject {

    private final RealSubject realSubject = new RealSubject();

    private void goHongKong() {
        System.out.println("前往香港");
    }

    @Override
    public void buy(String goodsName) {
        goHongKong();
        realSubject.buy(goodsName);
        sendGoods();
    }

    private void sendGoods() {
        System.out.println("把商品寄送給客户");
    }
}

public class Client {

    public static void main(String[] args) {
        Subject subject = new ProxyBuy();
        subject.buy("奶粉");
        System.out.println("----------");
        subject.buy("iphone");
//        运行结果:
//        前往香港
//        购买:奶粉
//        把商品寄送給客户
//        ----------
//        前往香港
//        购买:iphone
//        把商品寄送給客户
    }

}

在实际开发中,代理类的实现比上述代码要复杂得到。代理模式根据其目的和实现方式的不同可分为很多类。

远程代理

为一个位于不同的地址空间的对象提供一个本地的代理对象。

虚拟代理

如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

保护代理

控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

缓存代理

为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

智能引用代理

当一个对象被引用时,提供一些额外的操作。例如将对象被调用的次数记录下来。

表 常用的5种代理模式

1.1 虚拟代理

对于一些占用系统资源较多或者加载时间较长的对象,可以给对象提供一个虚拟代理。在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象。

1.1.1 适用场景

1) 由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间,此时可以用一个加载时间较短的代理对象来代表真实对象。在程序启动时,可以用代理对象代替真实对象初始化,大大加速系统的启动时间。

2) 当一个对象的加载是否耗费系统资源时。虚拟代理可以让那些占用大量内存或者处理起来非常复杂的对象推迟到使用它们的时候才场景,而在之前用一个相对来说占用资源较少的代理对象来代表真实对象,再通过代理对象来引用真实对象。

需求:在面试的时候,一般都最少有两轮面试,一面由项目经理负责,二面由技术总监面。而最终的录取结果是由技术总监决定的。(技术总监工资更高,如果每个面试者都由总监面,那么将会花费总监很多时间,进而给公司带来更多的花费)。

分析:技术总监属于占用资源较多的对象,不能频繁的调用。而产品经理占用的资源相对较小。在这个需求中,虚拟代理为产品经理。先一轮面试,把筛选后的名单给技术总监。

图 需求实现结构图

public interface Interviewer {

    boolean audition(String name, boolean isLastOne);

}

public class TechnicalDirector implements Interviewer{

    @Override
    public boolean audition(String name, boolean isLastOne) {
        Random random = new Random();
        return random.nextInt() % 3 == 0;
    }

}

public class ProjectManager implements Interviewer{

    private final List<String> passList = new ArrayList<>();

    @Override
    public boolean audition(String name, boolean isLastOne) {
        Random random = new Random();
        boolean pass = random.nextInt() % 5 == 0;
        if (pass) passList.add(name);
        if (isLastOne) cassTechnicalDirector();
        return pass;
    }

    /**
     * 一轮面试完成后,再叫技术总监来面试
     */
    private void cassTechnicalDirector(){
        TechnicalDirector technicalDirector = new TechnicalDirector();
        Iterator<String> iterator = passList.iterator();
        List<String> tempList = new ArrayList<>();
        while (iterator.hasNext()) {
            String next = iterator.next();
            if (technicalDirector.audition(next, !iterator.hasNext())) {
                tempList.add(next);
            }
        }
        if (tempList.size() == 0) {
            System.out.println("没人通过面试");
        } else {
            System.out.println("以下人员通过面试:");
            System.out.println(tempList);
        }
    }
}

public class Client {

    public static void main(String[] args) {
        Interviewer interviewer = new ProjectManager();
        for (int i = 0; i < 5000; i++) {
            interviewer.audition("路人" + i, i == 19);
        }
//        运行结果:
//        以下人员通过面试:
//        [路人5, 路人14, 路人16]

    }

}

​​​​​​​1.2 Java动态代理

在传统的代理模式中,客户端通过Proxy类调用RealSubject类的request()方法,同时可以在代理类中封装其他方法。而代理类和真实主题类都应该是事先已经存在的。如果需要为不同的真实主题类提供代理类,或者代理一个真实主题类中不同的方法,都需要增加新的代理类,这将导致系统的类格式急剧增加。

动态代理可以让系统能够根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类,而且可以代理不同的方法。

从JDK1.3开始,Java提供了对动态代理的支持。

1.2.1 InvocationHandler接口

是代理处理程序类的实现接口。作为代理实例的调用处理者的公共父类,该接口只声明了一个方法:

invoke(proxy,method,args): 用于处理对代理实例的方法调用并返回相应结果。第1个参数表示代理类的实例,第2个参数表示需要代理的方法,第3个表示代理方法的参数数组。

1.2.2 Proxy类

图 Proxy类的部分方法

最常用的方法是:

  1. newProxyInstance(classLoader,interfaces,invocationHandler), 返回一个动态创建的代理实例,第1个参数表示代理类的类加载器,第2个参数表示代理类所实现的接口列表(与真实主题类的接口列表一致),第3个参数表示所指派的调用处理程序类。
  2. getProxyClass(classLoader,class[]),返回一个Class类型的代理类,第1个参数是代理类的类加载器,第2个参数表示代理类所实现的接口列表。

1.2.3 动态代理实战

创建动态代理的步骤如下:

1)和静态代理相同,首先定义一个抽象主机角色,来定义代理类和真实主题类的真实接口。

public interface BuyTicket {

    boolean buyTicket(String start, String end);

}

2)创建被代理的类

public class Customer implements BuyTicket{
    @Override
    public boolean buyTicket(String start, String end) {
        System.out.println("购买" + start + "到" + end + "的火车票");
        Random random = new Random();
        return random.nextInt()  % 2 == 0;
    }
}

3)创建InvocationHandler类

public class BuyTicketInvocationHandler implements InvocationHandler {

    private final BuyTicket buyTicket;

    public BuyTicketInvocationHandler(BuyTicket buyTicket) {
        this.buyTicket = buyTicket;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        int num = 0; // 最多尝试次数
        boolean res = false;
        while (++num < 10 && !res) {
            System.out.println("黄牛党第" + num + "次操作");
            res = (boolean)method.invoke(buyTicket, args);
        }
        return res;
    }
}

4)创建动态代理对象

public class Client {

    public static void main(String[] args) {
        BuyTicket buyTicket = new Customer();
        BuyTicketInvocationHandler invocationHandler = new BuyTicketInvocationHandler(buyTicket);
        BuyTicket buyTicket1 = (BuyTicket) Proxy.newProxyInstance(BuyTicket.class.getClassLoader(), new Class<?>[]{BuyTicket.class}, invocationHandler);
        boolean res = buyTicket1.buyTicket("深圳", "赣州");
        if (res) System.out.println("购票成功");
        else System.out.println("购票失败");
//        运行结果:
//        黄牛党第1次操作
//        购买深圳到赣州的火车票
//        黄牛党第2次操作
//        购买深圳到赣州的火车票
//        购票成功
    }

}

2 远程代理

使得客户端可以访问远程主机(或另一个虚拟机)上的对象。远程主机可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求(或者是客户端不能直接访问远程主机中的业务对象)。

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

图 远程代理示意图

客户端无需关心实现具体业务的是谁,只需要按照服务接口所定义的方式直接与本地主机的代理对象交互即可。

2.1 RMI

Remote Method Invocation,远程方法调用。Java通过这个机制实现远程代理,实现一个Java虚拟机中的对象调用另一个Java虚拟机中对象的方法。

客户端通过一个桩(Stub)(相当于代理类)对象与远程主机上的业务对象进行通信。而远程主机端有个Skeleton(骨架)对象来负责与Stub对象通信。RMI基本步骤如下:

图 RMI基本步骤

  1. 客户端发起请求,将请求转交至RMI客户端的Stub类。
  2. Stub类将请求的接口、方法、参数等信息进行序列化。
  3. 将序列化后的流使用Socket传输到服务端。
  4. 服务端将接收到的流转发至相应的Skeleton类。
  5. Skeleton类将信息反序列化后调用实际业务处理类。
  6. 业务处理类处理完毕后将结果返回给Skeleton。
  7. Skeleton将结果序列化,再通过Socket将流传送到客户端Stub。
  8. Stub将接收到的流进行反序列化,将反序列化后得到的结果返回给客户端调用者。

2.2 RMI 实战

实现RMI的调用分为两大步:1)发布RMI服务;2)调用RMI服务。

2.2.1 发布RMI服务

发布一个RMI服务只需要做三件事:

  1. 定义一个RMI接口。
  2. 编写RMI接口的实现类。
  3. 通过JNDI发布RMI服务。
// 必须继承java.rmi.Remote,此外每个接口必须声明抛出一个java.rmi.RemoteException 异常
public interface BuyService extends Remote {

    String buyGoods(String goodsName) throws RemoteException;

}

// 必须继承java.rmi.server.UnicastRemoteObject, 而且必须提供一个构造器,并且构造器
// 必须抛出java.rmi.server.UnicastRemoteObject 异常
public class BuyServiceImpl extends UnicastRemoteObject implements BuyService{

    protected BuyServiceImpl() throws RemoteException {
    }

    @Override
    public String buyGoods(String goodsName) throws RemoteException {
        return "远程购买:" + goodsName;
    }
}

public class JNDIPublisher {

    public static void main(String[] args) throws RemoteException, MalformedURLException {
        BuyService buyService = new BuyServiceImpl();
        int port = 8089; //rmi服务端口
        String url = "rmi://localhost:" + port + "/BuyService"; //服务类的寻址符
        LocateRegistry.createRegistry(port);
        Naming.rebind(url,buyService);
    }

}

2.2.2 调用RMI服务

在另外一个项目(主机)中调用上面已发布的RMI服务。在调用服务时只有做两件事:

1)查找服务类的接口。

Object obj = Naming.lookup(url); // url 为上面定义的服务类的寻址符。

2)调用这个接口的方法。

调用接口方法有两个方法,1)通过反射;2)通过导入服务端的接口类(一定要导入,重写无效)。

public class RMICaller {

    public static void main(String[] args) {
        String url = "rmi://localhost:8089/BuyService";
        try {
            Object lookup = Naming.lookup(url);
            // 通过反射来调用方法
            Method method = lookup.getClass().getMethod("buyGoods",String.class);
            System.out.println(method.invoke(lookup, "奶粉"));;
//            运行结果:
//            远程购买:奶粉
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

3 代码模式优缺点

优点:

  1. 能协调调用者和被调用者,降低系统耦合度,符合迪米特法则。
  2. 客户端针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,复合开闭原则。
  3. 远程代理为位于不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提供系统的整体运行效率。
  4. 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
  5. 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。

缺点:

  1. 增加了代理对象,可能会造成请求的处理速度变慢。
  2. 实现代理需要额外的工作,有些代理模式的实现非常复杂。

4 适用场景

  1. 客户端对象需要访问远程主机中的对象。
  2. 需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象。例如一个对象需要很长时间才能完成加载时。
  3. 需要控制对一个对象的访问。
  4. 需要为某个被频繁访问的操作结果提供一个临时存储空间,以供对各客户端共享访问这些结果时。可以通过缓存代理。
  5. 需要为一个对象的访问提供一些额外的操作时。

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

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

相关文章

测试常见前端bug

目录 协作 测试方法 标签&#xff1a;标签 内容/ref/ 判断 arr&&arr.length 交互 样式不生效&#xff1a;devtools查找&#xff0c;编译前的标签&#xff0c;运行时不一定存在 可交互的需要提示 hover样式 没有交互逻辑&#xff0c;就不要设置交互 无法交互…

pytorch学习——模型选择

一.概念 模型选择是机器学习中的重要环节&#xff0c;它涉及到从各种统计&#xff0c;机器学习或深度学习模型中选取最佳模型的过程。这涉及到许多关键概念&#xff0c;包括偏差与方差&#xff0c;过拟合与欠拟合&#xff0c;训练误差和泛化误差&#xff0c;交叉验证&#xff0…

【Linux下6818开发板(ARM)】硬件空间挂载

(꒪ꇴ꒪ ),hello我是祐言博客主页&#xff1a;C语言基础,Linux基础,软件配置领域博主&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff01;送给读者的一句鸡汤&#x1f914;&#xff1a;集中起来的意志可以击穿顽石!作者水平很有限&#xff0c;如果发现错误&#x…

HTML基础知识点总结

目录 1.HTML简介 2.HTML基础结构 主要字符&#xff1a; 3.基础知识 &#xff08;一&#xff09;p标签 &#xff08;二&#xff09;hr标签 &#xff08;三&#xff09;尖角号 &#xff08;四&#xff09;版权号 (五)div和span div span (六)列表 &#xff08;1&…

Python爬虫实战(进阶篇)—6获取微某博信息(附完整代码)

转眼将就来到了我们爬虫基础课的第 6 节课,今天我们来获取微某博信息来进行阅读学习! PS前面几节课的内容在专栏这里,欢迎大家考古:点我 首先第一步我们先登录一下微x博:点我 点击左上角的搜索框,找到你想获取的用户: 大家可以看到这里有两种搜索方式: 1、按照关键字…

linux+Jenkins+飞书机器人发送通知(带签名)

文章目录 如何使用在linux 上安装python 环境发送消息python脚本把脚本上传倒linux上 jenkins 上执行脚本 如何使用 自定义机器人使用指南飞书官网https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot 在linux 上安装python 环境 yum install python3 python…

Skin Shader 使用自动生成的Thickness

Unity2023.2的版本&#xff0c;Thickness 自动化生成&#xff0c;今天测试了一把&#xff0c;确实不错。 1.Render 设置 在Project Settings->Graphics->HDRP Global Settings中 Frame Setting->Rendering->Compute Thickness 打开 2.Layer设置 2.1添加Layer&…

python基本知识学习

一、输出语句 在控制台输出Hello,World! print("Hello,World!") 二、注释 单行注释&#xff1a;以#开头 # print("你好") 多行注释&#xff1a; 选中要注释的代码Ctrl/三单引号三双引号 # print("你好") # a1 # a2 print("Hello,World!&…

【c语言进阶】字符函数和字符串函数知识总结

字符函数和字符串函数 前期背景求字符串长度函数strlen函数strlen函数三种模拟实现 长度不受限制的字符串函数strcpy函数strcpy函数模拟实现strcat函数strcat函数模拟实现strcmp函数strcmp函数模拟实现 长度受限制的字符串函数strncpy函数strncpy函数模拟实现strncat函数strnca…

推理和训练

监督学习与非监督学习 Supervised Learning有监督式学习: 输入的数据被称为训练数据&#xff0c;一个模型需要通过一个训练过程&#xff0c;在这个过程中进行预期判断&#xff0c;如果错误了再进行修正&#xff0c;训练过程一直持续到基于训练数据达到预期的精确性。其关键方法…

【Python机器学习】实验04(2) 机器学习应用实践--手动调参

文章目录 机器学习应用实践1.1 准备数据此处进行的调整为&#xff1a;要所有数据进行拆分 1.2 定义假设函数Sigmoid 函数 1.3 定义代价函数1.4 定义梯度下降算法gradient descent(梯度下降) 此处进行的调整为&#xff1a;采用train_x, train_y进行训练 1.5 绘制决策边界1.6 计算…

echarts遇到的问题

文章目录 折线图-区域面积图 areaStyley轴只有整数y轴不从0开始y轴数值不确定&#xff0c;有大有小&#xff0c;需要动态处理折线-显示label标线legend的格式化和默认选中状态x轴的lable超长处理x轴的相关设置 echarts各个场景遇到的问题 折线图-区域面积图 areaStyle areaStyl…

【JVM】JVM五大内存区域介绍

目录 一、程序计数器&#xff08;线程私有&#xff09; 二、java虚拟机栈&#xff08;线程私有&#xff09; 2.1、虚拟机栈 2.2、栈相关测试 2.2.1、栈溢出 三、本地方法栈&#xff08;线程私有&#xff09; 四、java堆&#xff08;线程共享&#xff09; 五、方法区&…

微信小程序 居中、居右、居底和横向、纵向布局,文字在图片中间,网格布局

微信小程序居中、居右、横纵布局 1、水平垂直居中&#xff08;相对父类控件&#xff09;方式一&#xff1a;水平垂直居中 父类控件&#xff1a; display: flex;align-items: center;//子控件垂直居中justify-content: center;//子控件水平居中width: 100%;height: 400px //注意…

go 查询采购单设备事项[小示例]V2-两种模式{严格,包含模式}

第一版&#xff1a; https://mp.csdn.net/mp_blog/creation/editor/131979385 第二版&#xff1a; 优化内容&#xff1a; 检索数据的两种方式&#xff1a; 1.严格模式--找寻名称是一模一样的内容&#xff0c;在上一个版本实现了 2.包含模式&#xff0c;也就是我输入检索关…

ps 给衣服换色

可以通过色相饱和度来改变颜色 但如果要加强对比 可以通过色阶或曲线来调整 针对整体 调整图层-色相/饱和度 着色 给整个画面上色 选区-遮罩-取出来 然后调整图层-色相/饱和度也可以 或者以有图层-色相饱和度后 选区 按ctrli使其遮罩 同时按alt鼠标左键单机 ctrli反相…

【SSM—SpringMVC】 问题集锦(持续更新)

目录 1.Tomcat启动&#xff0c;部署工件失败 1.Tomcat启动&#xff0c;部署工件失败 解决&#xff1a;使用SpringMVC&#xff0c;添加Web支持&#xff0c;要将项目结构进行添加WEB-INF下添加lib目录&#xff0c;将依赖添进去

解锁 Kotlin 中密封类(Seal Class)的能力:设计模式与代码组织的优化

解锁 Kotlin 中密封类(Seal Class)的能力&#xff1a;设计模式与代码组织的优化 多年来&#xff0c;我参与了多个项目&#xff0c;深知编写清晰、易维护代码的价值。最近在一个涉及大量数据类型处理的项目中&#xff0c;我发现使用密封类极大地提高了数据的组织和管理效率。此…

推动中小企业数字化转型,开利网络签约

随着数字经济的发展&#xff0c;大数据、区块链、物联网、AI等新兴数字化技术已成为一种趋势&#xff0c;对于产业园区而言&#xff0c;结合数字化技术形成的“数字园区”理念正逐渐出现在公众视野中。什么是“数字园区”&#xff1f;简单来说&#xff0c;通过对产业园区进行数…

<C语言> 动态内存管理

1.动态内存函数 为什么存在动态内存分配&#xff1f; int main(){int num 10; //向栈空间申请4个字节int arr[10]; //向栈空间申请了40个字节return 0; }上述的开辟空间的方式有两个特点&#xff1a; 空间开辟大小是固定的。数组在申明的时候&#xff0c;必须指定数组的…