设计模式——1_6 代理(Proxy)

诗有可解不可解,若镜花水月勿泥其迹可也

—— 谢榛

文章目录

  • 定义
  • 图纸
  • 一个例子:图片搜索器
    • 图片加载
    • 搜索器
      • 直接在Image添加
      • 组合他们
  • 各种各样的代理
    • 远程代理:镜中月,水中花
    • 保护代理:对象也该有隐私
    • 引用代理:我什么时候可以动手?
    • 虚拟代理:我们真的需要全部信息吗?
  • 碎碎念
    • 代理和装饰器
    • 最后一点碎碎念:代理和围墙

定义

为其他对象提供一种代理以控制对这个对象的访问


图纸

在这里插入图片描述



一个例子:图片搜索器

某天,你突发奇想,想做一个可以展示指定文件夹内所有图片的桌面应用。这个应用很简单,遍历文件夹,发现图片文件,把图片加载到GUI上的图片列表里,显示图片名和图片,就像这样:

在这里插入图片描述



图片加载

为了实现这样的效果,你编写了自己的 Image 类簇,并给予他一个通过 InputStream 来载入图片并获取图片信息的方法,就像这样:

在这里插入图片描述

我们可以通过 Image 中的 loadByStream 方法把参数输入流内的图片文件加载到内存中来,并把获取到的信息写到 messageMap 中,根据需要获取里面的内容反馈给 client

至此,不出意外的话我们根据 Image对象 提供的信息绘制出GUI后就可以得到截图类似的效果


搜索器

随着文件夹里面的图片越来越多,在里面找到你需要的变得越来越困难,于是新的想法出现了,你想要加一个搜索框,用于筛选读取到的图片

这个需求很合理,但是实现起来却出现了问题

Image 是通过把图片载入到内存的方式来获取图片信息的,这就意味着我要获取所有图片的文件名用于搜索之前必须加载所有的图片,这是无法接受的


经过我们分析,只要不把文件加载到内存里,只是遍历文件夹获取其中的所有文件的文件名是很快的,通过File

也就是说,对于一个文件来说,他在程序里会同时拥有对应的 Image对象 和 File对象

我当然希望这两个对象可以绑定在一起,那该怎么做呢?


直接在Image添加

直接添加到Image中,就像这样:

在这里插入图片描述

看起来很美好,也很必要

可是我要为将来考虑,这个程序里面的 Image 不一定都从硬盘上读取文件,我允许他从任何输入流中加载图片出来

这种时候file对象的存在就显得很尴尬,而且会导致getName方法的异常,我又得给他写 if(file==null)……

这显然是很糟糕的设计


组合他们

既然直接添加不可以,那很显然就只能用另一个类来把他们组合起来

但是怎么组合,有讲究

组合他们的这个类本质上还是Image,我并不是为了用这个类的对象来复制/删除文件……他的任务是包含了 Image 的任务的

所以我们可以考虑这样做:
在这里插入图片描述

public abstract class AbstractImage {public static final String KEY_NAME = "name";public static final String KEY_WIDTH = "width";protected final Map<String,Object> messageMap;public AbstractImage() {messageMap = new HashMap<>();}public abstract void loadByStream(InputStream is);public abstract String getName();public abstract Double getWidth();……
}public class Image extends AbstractImage {private volatile boolean isLoaded = false;//是否加载完毕@Overridepublic void loadByStream(InputStream is) {通过input流载入图片 载入后把isLoaded改为true}@Overridepublic String getName() {return isLoaded ? messageMap.get(KEY_NAME).toString() : null;}@Overridepublic Double getWidth() {Object o = messageMap.get(KEY_WIDTH);return isLoaded ? Double.parseDouble(o.toString()) : null;}
}public class ImageFileProxy extends AbstractImage {private final Image image;private File file;public ImageFileProxy(Image image) {this.image = image;}public void loadByFile(File file) throws FileNotFoundException {this.file = file;loadByStream(new FileInputStream(file));}@Overridepublic void loadByStream(InputStream is) {throw new RuntimeException("ImageFileProxy 中的loadByStream无法直接调用");}@Overridepublic String getName() {return file == null ? null : file.getName();}@Overridepublic Double getWidth() {return image.getWidth();}
}

我们为 AbstractImage 增加了了一个新子类 ImageFileProxy,这个类接收一个 Image 对象作为基底,绝大多数方法中他会直接调用传入的 Image对象 的实现,比如说当你调用 getWidth 的时候,其实和直接调用 Image 对象没有什么区别

当你访问特定的方法的时候(本例中的 getName),ImageFileProxy对象 内藏着的 File对象 会发挥作用,这样就实现了在没有把图片加载到内存的时候就可以获取到图片名称,从而实现边加载边查询

而当你直接访问 ImageFileProxy 中的 loadByStream 方法时,程序会直接报错,不允许你通过这种方式去直接加载图片

好,现在我们知道了上面的结构是怎么运作的,但为什么要这样写呢?

来看看 AbstractImage 的三个方法在 ImageFileProxy 中是怎么被实现的

  1. getWidth

    不做任何修改,直接调用被代理的 Image对象 的实现

  2. getName

    直接无视被代理对象的实现,完全改变接口的功能

  3. loadByStream

    不允许client直接访问,这相当于“封装”了这个方法。我们通过对 Image 中方法的访问进行“监视”的方式,来保护 ImageFileProxy对象状态 的完整性


不要小看这种写法,将来如果你突发奇想想在加载图片的时候增加一个加载进度条,也可以直接新增一个 AbstractImage 的代理子类来实现这样的需求,对已有的代码不会有什么影响。

而这正是一个标准的代理模式实现



各种各样的代理

远程代理:镜中月,水中花

远程代理是指在不同的地址空间里提供对相同内容的局部代表

是不是觉得这个定义老复杂了,emmmm,举个例子,比如说数组的复制,就像这样:

数组A里存着 X/Y/Z 三个对象,接着我们复制数组A得到数组B。数组A和B的内存地址当然是不一样的,但B里面存的还是 X/Y/Z。操作B,其实跟操作A没什么区别,其实此时B就能算是A的一个远程代理


真正让远程代理广为人知的是网络相关的开发

比如说现在我有Java写成的 服务器和N个客户机,我希望在服务器上有个按钮,点击后可以直接获取客户机上的硬件信息。

要做成这个效果,根据不同的连接方式,实现方法各不相同。其中一种是利用 RMI技术,让服务器直接调用客户机上运行的对象里的方法,并获取结果,这时候其实就是在服务器上建立客户机的 远程代理

是的,你没看错,我说的就是在服务器JVM上调用运行在其他JVM上的对象。这不是魔法,是真实可行的技术,同时他也是分布式的基础,也是远程代理大放异彩的舞台



保护代理:对象也该有隐私

当你需要管理N个具有相同根类对象的时候,十有八九会用到 容器,List也好,Set也好,或者数组、Map 这不重要

重要的是这些容器对象拥有对自己所存储的对象的完全掌控权,我的意思是说,client 可以随自己喜好对容器里面的内容增删改查,这完全不受控

不是所有的容器都允许随意往里添加或删除的内容的,这时候你就需要隐藏容器的某些接口


我们会再建一个类把真正的容器类封装起来,接着不提供被隐藏的接口的访问方式(或者根据不同的权限提供不同的行为)。而 client 只能和外部的 代理类 交互,至此实现对容器的保护,这就是保护代理



引用代理:我什么时候可以动手?

如果说工厂方法之类的模式提供了对一个对象的 创建行为的包装 的话,那么引用代理就是 对一个对象提供从创建到销毁全方面的包装

因为 client 只能通过代理类对象来访问被代理的对象,那么所有对被代理的对象的访问都是在代理类对象的监控之下的。只要你想,你可以知道被代理对象现在被多少个地方引用,他有没有进入某个有锁的区域,也可以决定被代理对象什么时候被初始化……

知道这些信息是有用的,打个比方:

  • 知道多少个引用:可以做引用计数、可以在丢失所有引用时释放资源
  • 了解状态是否被锁:可以控制别的对象不允许修改被代理对象的状态
  • 决定何时初始化:是第一次被访问时初始化?还是跟代理类对象一起被初始化?

引用代理可以给你的程序提供很多很偏门的优化手段哒



虚拟代理:我们真的需要全部信息吗?

代理模式可以提供对对象的一种访问控制

这种控制可以是限制对象公开的接口(保护代理);也可以用来管理被代理对象何时释放(引用代理

他甚至可以做到在某个内容没有被加载进来的情况下展示他的一部分,这就是虚拟代理

试想以下,当我们需要获取一个文件的名字和修改时间,有必要把整个文件都加载到内存里过一遍吗?

答案必然是否定的。要不然你打开【我的电脑】里面的目录一定要很久(因为你打开的时候需要过一遍文件夹里所有的文件

在Java程序里,我们用 File 来表示一个文件,通过 File 对象的方法我们可以获取到和他所代表的文件有关的各种信息

那么请问,File 在获取硬盘上的文件的信息的时候,真的每次都会把整个文件加载到程序中吗?

肯定没有啊。倒不如说在你用 IO流 让这个文件流入程序之前,硬盘上的那个文件根本没有被载入

也就是说 File对象 和硬盘上的那个文件之间,也存在一种代理关系

File 为我们提供了一系列操作文件和读取文件信息的接口;但是换句话来说, File对象 也控制着我们访问文件的路径和方式,让我们一定是按照 File类 的编写者的想法去跟文件交互,就像 getter&setter 一样。



碎碎念

代理和装饰器

你可能已经发现了,上例中的 ImageFileProxyImage 的关系不只是组合,他完全包装了后者。client 想要访问 Image 一定会受到 ImageFileProxy 的监视。这一点上装饰器也是这样的,装饰器包装别的对象的接口,为其添加职能

这样看起来代理和装饰器何其相似,但他们的区别也就在刚刚那句话里

请注意我的用词,对于代理,我说的是 监视;对于装饰器,我说的是 添加职能


这就是两者最大的区别:

  • 对代理来说,被代理对象的接口是被控制的,代理类可以决定整个调用过程最终的走向
  • 对装饰器来说,被装饰对象的接口是必须被调用的,装饰器只是为了给他添加功能

所以即使他们的实现很相似,但他们依然是不同的两种模式,因为目的截然不同


最后一点碎碎念:代理和围墙

围城里有句经典台词:城里的人想出去,城外的人想进来。

代理模式就像把守这座城池的卫兵一样,所有人出入这座城都要经过代理对象的盘问,以决定是否放行

对于围墙内的部分(被代理的部分)来说,难免会觉得卫兵不近人情,侵犯隐私,和围墙外的人相比毫无自由

而对围墙外的部分(client)来说,却觉得墙里个个是被保护得很好的花朵,虽然自由有了边界,却换回了惬意的生活

你说谁更幸福呢?

其实取决你自己,如果你在里面的时候只能看到围墙里的狼狈不堪;相信我,就算你诞生在外面,也只能看到围墙之外的一地鸡毛




万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AR 自回归模型

文章目录 总的代码ADF 检验(是否平稳)差分操作拟合AR 模型预测可视化总的代码 import pandas as pd import numpy as np import matplotlib.pyplot as plt from statsmodels.tsa.ar_model import AutoReg from statsmodels.tsa.stattools import adfuller# 生成一个示例时间序…

【github】使用github action 拉取国外docker镜像

使用github action 拉取国外docker镜像 k8s部署经常用到国外镜像&#xff0c;如果本地无法拉取可以考虑使用github action环境 github action的ci服务器在国外&#xff0c;不受中国防火墙影响github action 自带docker命令运行时直接将你仓库代码拉取下来 步骤 你的国内dock…

仿真机器人-深度学习CV和激光雷达感知(项目2)day03【机器人简介与ROS基础】

文章目录 前言机器人简介机器人应用与前景机器人形态机器人的构成 ROS基础ROS的作用和特点ROS的运行机制ROS常用命令 前言 &#x1f4ab;你好&#xff0c;我是辰chen&#xff0c;本文旨在准备考研复试或就业 &#x1f4ab;本文内容是我为复试准备的第二个项目 &#x1f4ab;欢迎…

手拉手JavaFX UI控件与springboot3+FX桌面开发

目录 javaFx文本 javaFX颜色 字体 Label标签 Button按钮 //按钮单击事件 鼠标、键盘事件 //(鼠标)双击事件 //键盘事件 单选按钮RadioButton 快捷键、键盘事件 CheckBox复选框 ChoiceBox选择框 Text文本 TextField(输入框)、TextArea文本域 //过滤 (传入一个参数&a…

开始学习vue2(Vue方法)

一、过滤器 过滤器&#xff08;Filters&#xff09;是 vue 为开发者提供的功能&#xff0c;常用于文本的格式 化。过滤器可以用在两个地方&#xff1a;插值表达式 和 v-bind 属性绑定。 过滤器应该被添加在 JavaScript 表达式的尾部&#xff0c;由“管道符 ”进行 调用&#…

USB-C接口给显示器带来怎样的变化?

随着科技的不断发展&#xff0c;Type-C接口已经成为现代电子设备中常见的接口标准。它不仅可以提供高速的数据传输&#xff0c;还可以实现快速充电和视频传输等功能。因此&#xff0c;使用Type-C接口的显示器方案也受到了广泛的关注。本文将介绍Type-C接口显示器的优势、应用场…

[MRCTF2020]你传你呢1

尝试了几次&#xff0c;发现是黑名单过滤&#xff0c;只要包含文件后缀有ph就传不了&#xff0c;同时也有类型检测&#xff0c;需要抓包修改content-type 尝试了上传.htaccess&#xff0c;成功了&#xff0c;可以利用这让服务器将jpg文件当作php来解析&#xff0c;详见超详细文…

【K8S 云原生】K8S的图形化工具——Rancher

目录 一、rancher概述 1、rancher概念 2、rancher和K8S的区别&#xff1a; 二、实验 1、安装部署 2、给集群添加监控&#xff1a; 3、创建命名空间&#xff1a; 4、创建deployment&#xff1a; 5、创建service&#xff1a; 6、创建ingress&#xff1a; 7、创建hpa 8…

qt-C++笔记之使用信号和槽实现跨类成员变量同步响应

qt-C笔记之使用信号和槽实现跨类成员变量同步响应 —— 杭州 2024-01-24 code review! 文章目录 qt-C笔记之使用信号和槽实现跨类成员变量同步响应1.运行2.main.cpp3.test.pro4.编译 1.运行 2.main.cpp 代码 #include <QCoreApplication> #include <QObject> #…

leetcode刷题(剑指offer) 105.从前序与中序遍历序列构造二叉树

105.从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,…

Webpack5 基本使用 - 1

Webpack 是什么 webpack 的核心目的是打包&#xff0c;即把源代码一个一个的 js 文件&#xff0c;打包汇总为一个总文件 bundle.js。 基本配置包括mode指定打包模式&#xff0c;entry指定打包入口&#xff0c;output指定打包输出目录。 另外&#xff0c;由于 webpack默认只能打…

kali安装LAMP和DVWA

LANMP简介 LANMP是指一组通常用来搭建动态网站或者服务器的开源软件&#xff0c;本身都是各自独立的程序&#xff0c;但是因为常被放在一起使用&#xff0c;拥有了越来越高的兼容度&#xff0c;共同组成了一个强大的Web应用程序平台。 L:指Linux&#xff0c;一类Unix计算机操作…

静态web服务器实战

准备html页面&#xff0c;包含两个页面(index.html, index2.html)和一个404(404html)页面&#xff0c;目录示意&#xff1a; 1.返回固定页面 with open("website/index.html","r") as file: import socket# # 返回固定的页面 website/index.html if __na…

静态分析C语言生成函数调用关系的利器——cally和egypt

大纲 准备工作安装graphviz安装cally安装egypt简单分析GCC产生RTL&#xff08;Register transfer language&#xff09;文件callyegypt总结 高级分析callyegypt 总结参考资料 在《静态分析C语言生成函数调用关系的利器——cflow》和《静态分析C语言生成函数调用关系的利器——c…

HarmonyOS鸿蒙学习基础篇 - Text文本组件

该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 Text文本组件是可以显示一段文本的组件。该组件从API Version 7开始支持&#xff0c;从API version 9开始&#xff0c;该接口支持在ArkTS卡片中使用。 子组件 可…

Flutter 页面嵌入 Android原生 View

前言 文章主要讲解Flutter页面如何使用Android原生View&#xff0c;但用到了Flutter 和 Android原生 相互通信知识&#xff0c;建议先看完这篇讲解通信的文章 Flutter 与 Android原生 相互通信&#xff1a;BasicMessageChannel、MethodChannel、EventChannel-CSDN博客 数据观…

Java面试题50道

文章目录 1.谈谈你对Spring的理解2.Spring的常用注解有哪些3.Spring中的bean线程安全吗4.Spring中的设计模式有哪些5.Spring事务传播行为有几种6.Spring是怎么解决循环依赖的7.SpringBoot自动配置原理8.SpringBoot配置文件类型以及加载顺序9.SpringCloud的常用组件有哪些10.说一…

rabbitmq基础-java-5、Topic交换机

1、简介 Topic类型的Exchange与Direct相比&#xff0c;都是可以根据RoutingKey把消息路由到不同的队列。 只不过Topic类型Exchange可以让队列在绑定BindingKey 的时候使用通配符&#xff01; BindingKey 一般都是有一个或多个单词组成&#xff0c;多个单词之间以.分割&#x…

(SSO单点登录)多个系统之间如何实现账号互通

SSO具有以下优点&#xff1a; 降低访问第三方网站风险&#xff1b;降低用户名和密码的管理成本&#xff1b;提高用户试用满意度&#xff1b;SSO使用标准的身份认证和授权协议&#xff0c;如OAuth、OpenID Connect等&#xff0c;可以保障用户身份的安全性和隐私性。 单点登录最大…

文件上传技术总结

语言可解析的后缀 &#xff08;前提&#xff1a;在Apache httpd.conf 配置文件中有特殊语言的配置 AddHandler application/x-httpd-php .php 搭配大小写、双重、空格来进行 其中&#xff1a; phtml、pht、php3、php4和php5都是Apache和php认可的php程序的文件后缀 常见的…