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

这篇具有很好参考价值的文章主要介绍了设计模式——1_6 代理(Proxy)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

—— 谢榛

定义

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


图纸

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



一个例子:图片搜索器

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

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



图片加载

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

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

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

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


搜索器

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

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

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


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

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

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


直接在Image添加

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

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

看起来很美好,也很必要

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

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

这显然是很糟糕的设计


组合他们

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

但是怎么组合,有讲究

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

所以我们可以考虑这样做:
设计模式——1_6 代理(Proxy),设计模式,设计模式,java,代理模式

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;//是否加载完毕

    @Override
    public void loadByStream(InputStream is) {
        通过input流载入图片 载入后把isLoaded改为true
    }

    @Override
    public String getName() {
        return isLoaded ? messageMap.get(KEY_NAME).toString() : null;
    }

    @Override
    public 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));
    }

    @Override
    public void loadByStream(InputStream is) {
        throw new RuntimeException("ImageFileProxy 中的loadByStream无法直接调用");
    }

    @Override
    public String getName() {
        return file == null ? null : file.getName();
    }

    @Override
    public 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)来说,却觉得墙里个个是被保护得很好的花朵,虽然自由有了边界,却换回了惬意的生活

你说谁更幸福呢?

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




万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容文章来源地址https://www.toymoban.com/news/detail-823767.html

到了这里,关于设计模式——1_6 代理(Proxy)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 设计模式之代理模式(Proxy)的C++实现

    在组件的开发过程中,有些对象由于某种原因(比如对象创建的开销很大,或者对象的一些操作需要做安全控制,或者需要进程外的访问等),会使Client使用者在操作这类对象时可能会存在问题:(1)使用者直接访问这种对象会使系统带来很多麻烦。(2)或者使用者使不能

    2024年02月12日
    浏览(41)
  • 设计模式——1_6 代理(Proxy)

    诗有可解不可解,若镜花水月勿泥其迹可也 —— 谢榛 为其他对象提供一种代理以控制对这个对象的访问 某天,你突发奇想,想做一个可以展示指定文件夹内所有图片的桌面应用。这个应用很简单,遍历文件夹,发现图片文件,把图片加载到GUI上的图片列表里,显示图片名和

    2024年01月25日
    浏览(45)
  • 设计模式之代理模式(Proxy),以C++为例,实现远程代理、虚拟代理、保护代理等。

            兄弟姐妹们好,又是好久没有更新了,今天给大家简单介绍代理模式,一个很简单的设计模式,旨在不改变原对象的情况下通过代理对象来控制对原对象的访问。代理模式根据具体情况还可以分为远程代理、虚拟代理、保护代理等,下面来介绍一下。 目录  一、代理

    2023年04月09日
    浏览(41)
  • 设计模式- 代理模式(Proxy Pattern)结构|原理|优缺点|场景|示例

                                        设计模式(分类)        设计模式(六大原则)        创建型(5种)         工厂方法         抽象工厂模式        单例模式        建造者模式        原型模式     结构型(7种)      

    2024年04月24日
    浏览(42)
  • Java设计模式 (三) 代理设计模式

    什么是代理设计模式? 代理设计模式是一种结构型设计模式,它允许创建一个代理对象,用于控制对其他对象的访问。代理模式通常用于在访问对象时添加一些附加操作,而不是直接访问真实对象。代理模式可以在不改变原始类代码的情况下,通过引入代理类来增强功能。 代

    2024年02月12日
    浏览(41)
  • 【Java设计模式005】代理模式

    由于一些特定原因某些对象不适合或者不能直接引用目标对象,这时就可以使用代理模式。代理模式为目标对象提供一个代理以控制访问对象对目标对象的访问。客户端只能直接访问代理对象,不能直接访问目标对象,这么做确保了目标对象的安全。生活中一个常见的例子就

    2024年02月12日
    浏览(41)
  • Java设计模式(十三)代理模式

    一、概述 代理模式是一种结构型设计模式,它提供了一个代理对象,充当被代理对象的接口,以控制对被代理对象的访问。代理模式可以在不修改被代理对象的情况下,增加额外的功能或控制访问方式。 二、代码 以下是一个示例代码,说明代理模式的使用: 在上述代码中,

    2024年02月04日
    浏览(35)
  • Java 与设计模式(13):代理模式

    代理模式是一种结构型设计模式,用于在访问对象时引入一个代理对象,以控制对实际对象的访问。代理对象充当了客户端和实际对象之间的中介,客户端通过代理对象间接地访问实际对象,从而可以在访问过程中添加额外的逻辑或控制。代理模式可以提供对实际对象的保护

    2024年02月09日
    浏览(37)
  • Java设计模式---单例 工厂 代理模式

    单例模式是设计模式中的一种,属于创建型模式。在软件工程中,单例模式确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于那些需要频繁实例化然后引用,且创建新实例的开销较大的类,例如数据库连接池、缓存管理等。 意图 :保证一个类仅有一个实例

    2024年01月24日
    浏览(50)
  • 基于Java的设计模式 - 代理模式

    代理模式是一种使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式。简单来讲就是在不修改目标对象的基础上,增强主业务逻辑的设计模式。 代理模式基本可分为三种 静态代理 JDK动态代理 CGLIB动态代理 上述简单分就是静态和动态代理,静

    2024年02月07日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包