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

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

一、什么是Proxy模式



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


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

     

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

76e13f2ab7c74fcc98e6e54c37a0f01e.png


二、Proxy模式示例代码



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


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


2.1 类之间的关系

     

类的功能表:

0555c0effcf64a868984f1734c30ba58.png

类图:

b446413e3d7c47bcb8499ba526cd91af.png

 时序图

497f7a15a7954142998f11dcdb0050a5.png

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 运行结果

800185ed9e03428abf83bbd91c4100df.png


三、拓展思路的要点



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模式中,与增加新功能相比,它更注重通过设置代理人的方式来减轻本人的工作负担。


 设计模式学习(十二):Decorator装饰器模式_玉面大蛟龙的博客-CSDN博客


相关文章
|
14天前
|
设计模式 缓存 安全
设计模式——代理模式
静态代理、JDK动态代理、Cglib 代理
设计模式——代理模式
|
2天前
|
设计模式 Java 数据安全/隐私保护
Java设计模式-代理模式(7)
Java设计模式-代理模式(7)
|
1月前
|
设计模式 缓存 Java
【十一】设计模式~~~结构型模式~~~代理模式(Java)
文章详细介绍了代理模式(Proxy Pattern),这是一种对象结构型模式,用于给对象提供一个代理以控制对它的访问。文中阐述了代理模式的动机、定义、结构、优点、缺点和适用环境,并探讨了远程代理、虚拟代理、保护代理等不同代理形式。通过一个商务信息查询系统的实例,展示了如何使用代理模式来增加身份验证和日志记录功能,同时保持客户端代码的无差别对待。此外,还讨论了代理模式在分布式技术和Spring AOP中的应用,以及动态代理的概念。
【十一】设计模式~~~结构型模式~~~代理模式(Java)
|
1月前
|
设计模式
设计模式的基础问题之代理模式在工作中的问题如何解决
设计模式的基础问题之代理模式在工作中的问题如何解决
|
2月前
|
设计模式 算法 Go
iLogtail设计模式问题之代理模式在iLogtail中是如何应用的
iLogtail设计模式问题之代理模式在iLogtail中是如何应用的
|
2月前
|
设计模式 缓存 JavaScript
js设计模式【详解】—— 代理模式
js设计模式【详解】—— 代理模式
26 0
|
3月前
|
设计模式 监控 安全
设计模式之代理模式(Java)
设计模式之代理模式(Java)
|
14天前
|
设计模式 算法 安全
设计模式——模板模式
模板方法模式、钩子方法、Spring源码AbstractApplicationContext类用到的模板方法
设计模式——模板模式
|
6天前
|
设计模式 数据库连接 PHP
PHP中的设计模式:如何提高代码的可维护性与扩展性在软件开发领域,PHP 是一种广泛使用的服务器端脚本语言。随着项目规模的扩大和复杂性的增加,保持代码的可维护性和可扩展性变得越来越重要。本文将探讨 PHP 中的设计模式,并通过实例展示如何应用这些模式来提高代码质量。
设计模式是经过验证的解决软件设计问题的方法。它们不是具体的代码,而是一种编码和设计经验的总结。在PHP开发中,合理地使用设计模式可以显著提高代码的可维护性、复用性和扩展性。本文将介绍几种常见的设计模式,包括单例模式、工厂模式和观察者模式,并通过具体的例子展示如何在PHP项目中应用这些模式。
|
4天前
|
设计模式 Java Spring
spring源码设计模式分析-代理设计模式(二)
spring源码设计模式分析-代理设计模式(二)