设计模式学习(八):Proxy代理模式

news2024/11/24 14:37:06

一、什么是Proxy模式

        Proxy是“代理人”的意思,它指的是代替别人进行工作的人。当不一定需要本人亲自进行工作时,就可以寻找代理人去完成工作。但代理人毕竟只是代理人,能代替本人做的事情终究是有限的。因此,当代理人遇到无法自己解决的事情时就会去找本人解决该问题。

        在面向对象编程中,“本人”和“代理人”都是对象。如果“本人”对象无法自己亲自完成一些工作,就将其交给“代理人”对象负责。

        用一句话概况:只在必要时生成实例。

二、Proxy模式示例代码

        这段示例程序实现了一个“带名字的打印机”。说是打印机,其实只是将文字显示在界面上而已。在Main类中会生成PrinterProxy类的实例(即“代理人”)。首先我们会给实例赋予名字Alice并在界面中显示该名字。接着会将实例名字改为Bob,然后显示该名字。在设置和获取名字时,都不会生成真正的Printer类的实例(即本人),而是由PrinterProxy类代理。最后,直到我们调用print方法,开始进入实际打印阶段后,PrinterProxy类才会生成Printer类的实例。

        为了让PrinterProxy类与Printer类具有一致性,我们定义了Printable接口。示例程序的前提是“生成Printer类的实例”这一处理需要花费很多时间。为了在程序中体现这一点,我们在Printer类的构造函数中调用了heavyJob方法,让它干一些“重活”——睡眠5秒钟。

2.1 类之间的关系

        类的功能表: 

        类图:

 

        时序图:

 2.2 Printer类

        Printer类(代码清单21-1)是表示“本人”的类。

        需要注意的是:heavyJob表示一个重活,也就是每秒只显示一个点,Printer类会因为构造函数里有该方法而不方便生成实例,所以我们在最后打印时再生成实例。也说明了前面讲的:只在必要时生成实例。

/**
 * 打印机“本人”
 */
public class Printer implements Printable {

    private String name;

    public Printer() {
        //构造函数里有“重活”,不方便打印,因此要将打印工作交给代理
        heavyJob("正在生成Printer的实例");
    }
    public Printer(String name) {
        this.name = name;
        heavyJob("正在生成Printer的实例(" + name + ")");
    }

    /**
     * 设置打印机的名字
     */
    @Override
    public void setPrinterName(String name) {
        this.name = name;
    }

    /**
     * 获取打印机的名字
     */
    @Override
    public String getPrinterName() {
        return name;
    }

    @Override
    public void print(String string) {
        System.out.println("=== " + name + " ===");
        System.out.println(string);
    }

    /**
     * 一个重活,每秒只显示一个点,Printer类会因为构造函数里有该方法
     * 而不方便打印,所以我们才将打印工作交给代理
     */
    private void heavyJob(String msg) {
        System.out.print(msg);
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.print(".");
        }
        System.out.println("结束。");
    }
}

2.3 Printable接口

        Printable接口用于使PrinterProxy类和Printer类具有一致性。

public interface Printable {
    //设置打印机的名字
    public abstract void setPrinterName(String name);
    //获取打印机的名字
    public abstract String getPrinterName();
    //显示文字(打印输出)
    public abstract void print(String string);
}

2.4 PrinterProxy类

        PrinterProxy类是扮演“代理人”角色的类,它实现了Printable接口。

       print方法已经超出了代理人的工作范围,因此它会调用realize方法来生成本人。Realize有“实现”(使成为真的东西)的意思。在调用realize方法后,real字段中会保存本人( Print类的实例),因此可以调用real.print方法。这就是“委托”

        不论setPrinterName方法和getPrinterName方法被调用多少次,都不会生成Printer类的实例。只有当真正需要本人时,才会生成Printer类的实例( PrinterProxy类的调用者完全不知道是否生成了本人,也不用在意是否生成了本人)。

        这里希望大家记住的是,Printer类并不知道PrinterProxy类的存在。即,Printer类并不知道自己到底是通过PrinterProxy被调用的还是直接被调用的。

        但反过来,PrinterProxy类是知道Printer类的。这是因为PrinterProxy类的real字段是Printer类型的。在PrinterProxy类的代码中,显式地写出了Printer这个类名。因此,PrinterProxy类是与Printer类紧密地关联在一起的组件,事实上他们也是可以解耦的。

        相信细心的读者应该已经发现了Printer类的setPrinterName方法和realize方法都是synchronized方法。如果不使用synchronized,当多个线程几乎同时调用该方法时,在判断是否已经生成实例时可能会出错,导致new出多个实例。

public class PrinterProxy implements Printable{

    //名字
    private String name;
    //“本人”
    private Printer real;

    public PrinterProxy() {}
    public PrinterProxy(String name) {
        this.name = name;
    }

    /**
     * setPrinterName方法用于设置新的打印机名字。
     * 如果real字段不为null(也就是已经生成了“本人”),那么会设置“本人”的名字",
           同时设置自己( PrinterProxy的实例)的名字。
     * 但是当real字段为null时(即还没有生成“本人”),那么只会设置自己( PrinterProxy的实例)的名字。
     */
    @Override
    public synchronized void setPrinterName(String name) {
        if (real != null) {
            real.setPrinterName(name);
        }
        this.name = name;
    }

    /**
     * 返回自己的name字段
     * @return 自己的name字段
     */
    @Override
    public String getPrinterName() {
        return name;
    }

    /**
     * print方法已经超出了代理人的工作范围,因此它会调用realize方法来生成本人
     * @param string 打印机的名字
     */
    @Override
    public void print(String string) {
        realize();
        real.print(string);
    }

    /**
     * 调用realize方法后,real字段中会保存本人(Print类的实例),
     * 因此可以调用real.print方法。这就是“委托”
     */
    private synchronized void realize() {
        if (real == null) {
            real = new Printer(name);
        }
    }
}

2.5 Main类

        Main类通过PrinterProxy类使用Printer类。Main类首先会生成PrinterProxy,然后调用getPrinterName方法获取打印机名并显示它。之后通过setPrinterName方法重新设置打印机名。最后,调用print方法输出"Hello.world."。

        请注意,在设置名字和显示名字之间并没有生成Printer的实例(本人),直至调用print方法后,Printer的实例才被生成。

public class Main {
    public static void main(String[] args) {
        Printable p = new PrinterProxy("Alice");
        System.out.println("现在的名字是 " + p.getPrinterName() + "。");
        p.setPrinterName("Bob");
        System.out.println("现在的名字是 " + p.getPrinterName() + "。");
        p.print("Hello, world.");
    }
}

2.6 运行结果

 

三、拓展思路的要点 

3.1 使用代理人来提升处理速度

       在Proxy模式中,Proxy 角色作为代理人尽力肩负着工作使命。例如,在示例程序中,通过使用Proxy 角色,我们成功地将耗时处理(生成实例的处理)推迟至print方法被调用后才进行。

        示例程序中的耗时处理的消耗时间并不算太长,大家可能感受不深。请大家试想一下,假如在一个大型系统的初始化过程中,存在大量的耗时处理。如果在启动系统时连那些暂时不会被使用的功能也初始化了,那么应用程序的启动时间将会非常漫长,这将会引发用户的不满。而如果我们只在需要使用某个功能时才将其初始化,则可以帮助我们改善用户体验。

3.2 有必要划分代理人和本人吗

        当然,我们也可以不划分PrinterProxy类和Printer类,而是直接在Printer类中加入惰性求值功能(即只有必要时才生成实例的功能)。不过,通过划分PrinterProxy角色和Printer角色,可以使它们成为独立的组件,在进行修改时也不会互相之间产生影响(分而治之)。

        只要改变了PrinterProxy类的实现方式,即可改变在Printable接口中定义的那些方法,即对于“哪些由代理人负责处理,哪些必须本人负责处理”进行更改。而且,不论怎么改变,都不必修改Printer类。如果不想使用惰性求值功能,只需要修改Main类,将它使用new关键字生成的实例从PrinterProxy类的实例变为Printer类的实例即可。由于PrinterProxy类和Printer类都实现了Printable接口,因此Main类可以放心地切换这两个类。

        在示例程序中,PrinterProxy类代表了“Proxy角色”。因此使用或是不使用PrinterProxy类就代表了使用或是不使用代理功能。

3.3 代理与委托

        代理人只代理他能解决的问题。当遇到他不能解决的问题时,还是会“转交”给本人去解决。这里的“转交”就是在本书中多次提到过的“委托”。从PrinterProxy类的print方法中调用real.print方法正是这种“委托”的体现。

        在现实世界中,应当是本人将事情委托给代理人负责,而在设计模式中则是反过来的。

3.4 透明性

        PrinterProxy类和Printer类都实现了Printable接口,因此Main类可以完全不必在意调用的究竟是PrinterProxy类还是Printer类。无论是直接使用Printer类还是通过PrinterProxy类间接地使用Printer类都可以。

        在这种情况下,可以说PrinterProxy类是具有“透明性”的。就像在人和一幅画之间放置了一块透明的玻璃板后,我们依然可以透过它看到画一样,即使在Main类和Printer类之间加入一个PrinterProxy类,也不会有问题。

3.5 HTTP代理

        提到代理,许多人应该都会想到HTTP代理。HTTP代理是指位于HTTP服务器(Web服务器)和HTTP客户端(Web浏览器)之间,为Web页面提供高速缓存等功能的软件。我们也可以认为它是一种 Proxy模式。

        HTTP代理有很多功能。作为示例,我们只讨论一下它的页面高速缓存功能。

        通过Web浏览器访问Web页面时,并不会每次都去访问远程Web服务器来获取页面的内容,而是会先去获取HTTP代理缓存的页面。只有当需要最新页面内容或是页面的缓存期限过期时,才去访问远程Web服务器。

        在这种情况下,Web服务器扮演的是Client 角色,HTTP代理扮演的是Proxy角色,而Web服务器扮演的则是RealSubject角色。

3.6 各种Proxy模式

Proxy模式有很多种变化形式。

  • Virtual Proxy(虚拟代理)

        Virtual Proxy就是本章中学习的Proxy模式。只有当真正需要实例时,它才生成和初始化实例。

  • Remote Proxy(远程代理)

        Remote Proxy可以让我们完全不必在意RealSubject角色是否在远程网络上,可以如同它在自己身边一样(透明性地)调用它的方法。Java的RMI (RemoteMethodInvocation:远程方法调用)就相当于Remote Proxy。

  • Access Proxy

        Access Proxy用于在调用RealSubject角色的功能时设置访问限制。例如,这种代理可以只允许指定的用户调用方法,而当其他用户调用方法时则报错。

四、相关的设计模式

4.1 Adapter模式

        Adapter模式适配了两种具有不同接口(API)的对象,以使它们可以一同工作。

        而在Proxy模式中,Proxy角色与RealSubject角色的接口(API)是相同的(透明性)。

        设计模式学习(三):Adapter适配器模式_玉面大蛟龙的博客-CSDN博客

4.2 Decorator模式

        Decorator模式与Proxy模式在实现上很相似,不过它们的使用目的不同。

        Decorator模式的目的在于增加新的功能。而在Proxy模式中,与增加新功能相比,它更注重通过设置代理人的方式来减轻本人的工作负担。

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

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

相关文章

文件上传oss,并查询上传进度(SpringBoot+Redis+Oss+Swagger3)

文章目录诉求技术选型pom配置项目结构文件树图示结构代码实现配置相关配置文件yamlSwagger3配置跨域问题配置oss相关ServiceControllerApplicationSwagger接口操作获取上传文件标识号获取文件上传进度小结诉求 将文件上传到oss&#xff0c;并实时监听上传进度&#xff0c;并将进…

【javaSE】中基本类型和引用类型对象的比较及PriorityQueue中的比较方法

写博客是为了提升自己&#xff0c;也是为了展现自己的学习成果&#xff0c;坚持!坚持!坚持&#xff01;未来是什么样的&#xff0c;闯一闯就知道啦。喜欢就留个关注吧&#xff01;&#xff01;! 目录 一、java对象的比较 1.1java中基本类型的比较 1.2引用对象的比较 1.3引用…

使用云端的GPU进行yolov5的训练

前言本文介绍了使用云端GPU进行yolov5训练环境配置的过程一、创建实例这里使用的是恒源云的GPU服务器&#xff0c;官方网址为恒源云_GPUSHARE-恒源智享云他的用户文档为Tmux - 恒源云用户文档一般的问题在用户文档中都可以找到解决办法。注册并登录后的界面如下图所示。点击云市…

c++11 标准模板(STL)(std::forward_list)(十)

定义于头文件 <forward_list> template< class T, class Allocator std::allocator<T> > class forward_list;(1)(C11 起)namespace pmr { template <class T> using forward_list std::forward_list<T, std::pmr::polymorphic_…

UPerNet:Unified Perceptual Parsing for Scene Understanding论文解读

Unified Perceptual Parsing for Scene Understanding 论文&#xff1a;[1807.10221] Unified Perceptual Parsing for Scene Understanding (arxiv.org) 代码&#xff1a;CSAILVision/unifiedparsing: Codebase and pretrained models for ECCV’18 Unified Perceptual Parsi…

第二章.线性回归以及非线性回归—岭回归

第二章.线性回归以及非线性回归 2.12 岭回归&#xff08;Ridge Regression&#xff09; 1.前期导入&#xff1a; 1).标准方程法[w(XTX)-1XTy]存在的缺陷&#xff1a; 如果数据的特征比样本点还多&#xff0c;数据特征n&#xff0c;样本个数m&#xff0c;如如果n>m&#xf…

5种气血不足的面相

我们常用“气色好”形容人良好的健康状态&#xff0c;反之&#xff0c;气血不足就是不健康的表现。想知道自己是否气血不足&#xff0c;可以从以下几种表现中判断。眼白黄&#xff1a;所谓人老珠黄&#xff0c;就是指眼白的颜色变得浑浊、发黄、有血丝&#xff0c;很可能气血不…

网络编程基础(1)

1 OSI七层模型&#xff08;理论&#xff09; 七层模型&#xff0c;亦称OSI&#xff08;Open System Interconnection&#xff09;。参考模型是国际标准化组织&#xff08;ISO&#xff09;制定的一个用于计算机或通信系统间互联的标准体系&#xff0c;一般称为OSI参考模型或七层…

cycle_gan使用教程

junyanz/pytorch-CycleGAN-and-pix2pix: Image-to-Image Translation in PyTorch (github.com) 如果是用cycle_gan 数据集 /数据集文件夹名&#xff0c;下面四个子文件名 testA testB trainA trainB trainA是A风格图片&#xff0c;trainB是B风格图片。 训练参数 test…

CCF BDCI | 算能赛题决赛选手说明论文-04

基于TPU平台实现人群密度估计 队名&#xff1a;innovation 陈照照 数据科学与大数据技术20级 台州学院 中国-瑞安 479253198qq.com董昊数据科学与大数据技术20级 台州学院 中国-杭州 donghaowifi163.com陈晓聪数据科学与大数据技术20级 台州学院 中国-宁波 2637491…

Golang -- openwechat微信发送消息、自动回复

开篇 马上就要到农历新年了&#xff0c;不妨写一段代码准时为好友们送上祝福。 该 Demo 使用开源项目 openwechat &#xff0c;实现获取好友列表、为好友发送消息、图片或文件&#xff0c;接收来自好友或群组的消息并设置自动回复等功能。 openwechat Github地址 openwechat 文…

管道(匿名,有名)

文章目录Linux 进程间通信的方式管道匿名管道有名管道Linux 进程间通信的方式 管道 管道特点 管道其实是一个在内核内存中维护的缓冲器&#xff0c;这个缓冲器的存储能力是有限的&#xff0c;不同的操作系统大小不一定相同管道拥有文件的特质&#xff1a;读操作、写操作 匿名管…

线扫相机DALSA-变行高拍照

CamExpert在线阵模式中默认的Buffer设置是Fixed Length。在这种设置下&#xff0c;在一帧采集结束前所接收到的新的帧触发信号都会被忽略。在有的应用中&#xff0c;需要新一帧的外触发信号能够中断当前帧的采集&#xff0c;开始新的一帧。这需要将Buffer设为Variable Length。…

【云原生】k8s之HPA,命名空间资源限制

内容预知 1.HPA的相关知识 2.HPA的部署运用 2.1 进行HPA的部署设置 2.2 HPA伸缩的测试演示 &#xff08;1&#xff09;创建一个用于测试的pod资源 (2)创建HPA控制器&#xff0c;进行资源的限制&#xff0c;伸缩管理 &#xff08;3&#xff09;进入其中一个pod容器仲&#xf…

Redhat OpenStack使用命令行发放云主机

OpenStack中各大组件的作用Glance&#xff1a;负责管理镜像&#xff08;镜像的上传、删除、下载&#xff09;Swift&#xff1a;提供镜像存储的空间Nova&#xff1a;负责配额的修改、启动云主机&#xff08;实例&#xff09;、创建密钥对、绑定弹性IP等Keystone&#xff1a;提供…

jQuery(二):属性、元素、尺寸位置操作、事件

jQuery属性操作内容文本值元素操作尺寸、位置操作事件注册事件处理事件对象拷贝对象属性操作 1.获取固有属性语法 prop(‘‘属性’’) 固有属性就是html自带的&#xff0c;例如a元素里面的 href &#xff0c;input 元素里面的 type。 2.设置固有属性语法 prop(‘‘属性’’, …

Python NumPy 搜索 数组

前言NumPy&#xff08;Numerical Python的缩写&#xff09;是一个开源的Python科学计算库。使用NumPy&#xff0c;就可以很自然地使用数组和矩阵。NumPy包含很多实用的数学函数&#xff0c;涵盖线性代数运算、傅里叶变换和随机数生成等功能。本文主要介绍Python NumPy 搜索 数组…

Linux小黑板(8)管道

"让我们,笑吧"一、什么是通信?管道是属于进程间通信的一个实现方式。再讲管道之前呢&#xff0c;我们先来说说什么叫做进程间通信。我们日常生活中&#xff0c;给自己的家人、朋友给一个call&#xff0c;或者弹一条微信、QQ等等&#xff0c;从而让人家能够知道我们想…

Databend 开源周报第 76 期

英文版移步&#xff1a;https://databend.rs/blog/2023-01-11-databend-weekly Databend 是一款强大的云数仓。专为弹性和高效设计。自由且开源。即刻体验云服务&#xff1a;https://app.databend.com 。 What’s New 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的…

vue 中由浅拷贝引发问题的一些场景

在工作的过程中踩了很多的由浅拷贝导致的坑&#xff0c;今天总结在这里&#xff0c;希望对大家有所帮助 1. 组件中直接抛出一个引用类型变量 &#x1f330;举个例子 &#xff08;ps: 以下代码为伪代码&#xff0c;主要展示逻辑用&#xff09; 子组件&#xff08;uploadImg&a…