设计模式学习(八):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博客


相关文章
|
2月前
|
设计模式 缓存 安全
设计模式——代理模式
静态代理、JDK动态代理、Cglib 代理
设计模式——代理模式
|
2月前
|
设计模式 Java 数据安全/隐私保护
Java设计模式-代理模式(7)
Java设计模式-代理模式(7)
|
3月前
|
设计模式 缓存 Java
【十一】设计模式~~~结构型模式~~~代理模式(Java)
文章详细介绍了代理模式(Proxy Pattern),这是一种对象结构型模式,用于给对象提供一个代理以控制对它的访问。文中阐述了代理模式的动机、定义、结构、优点、缺点和适用环境,并探讨了远程代理、虚拟代理、保护代理等不同代理形式。通过一个商务信息查询系统的实例,展示了如何使用代理模式来增加身份验证和日志记录功能,同时保持客户端代码的无差别对待。此外,还讨论了代理模式在分布式技术和Spring AOP中的应用,以及动态代理的概念。
【十一】设计模式~~~结构型模式~~~代理模式(Java)
|
3月前
|
设计模式
设计模式的基础问题之代理模式在工作中的问题如何解决
设计模式的基础问题之代理模式在工作中的问题如何解决
|
4月前
|
设计模式 算法 Go
iLogtail设计模式问题之代理模式在iLogtail中是如何应用的
iLogtail设计模式问题之代理模式在iLogtail中是如何应用的
|
4月前
|
设计模式 缓存 JavaScript
js设计模式【详解】—— 代理模式
js设计模式【详解】—— 代理模式
32 0
|
5月前
|
设计模式 监控 安全
设计模式之代理模式(Java)
设计模式之代理模式(Java)
|
15天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
17天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###