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

示例程序:

名字 说明
Printer 表示带名字的打印机的类(本人)
Printable Printer和PrinterProxy的共同接口
PrinterProxy 表示带名字的打印机的类(代理人)
Main 测试程序行为的类

Printer类:

Printer类是表示“本人”的类。在构造函数中,去做一些所谓的“重活”。Proxy模式的核心是PrinterProxy类。Printer类自身并不难理解。

  1. public class Printer implements Printable {
  2. private String name;
  3. public Printer() {
  4. heavyJob("Printer的实例生成中");
  5. }
  6. /**
  7. * @Description: 构造函数
  8. * @Param: [name]
  9. * @return:
  10. * @Author: 汪泉
  11. * @Date: 2022/6/8 14:34
  12. **/
  13. public Printer(String name) {
  14. this.name = name;
  15. heavyJob("Printer的实例生成中(" + name + ")");
  16. }
  17. /**
  18. * @Description: 设置名字
  19. * @Param: [name]
  20. * @return: void
  21. * @Author: 汪泉
  22. * @Date: 2022/6/8 14:35
  23. **/
  24. @Override
  25. public void setPrinterName(String name) {
  26. this.name = name;
  27. }
  28. /**
  29. * @Description: 获取名字
  30. * @Param: []
  31. * @return: java.lang.String
  32. * @Author: 汪泉
  33. * @Date: 2022/6/8 14:35
  34. **/
  35. @Override
  36. public String getPrinterName() {
  37. return name;
  38. }
  39. /**
  40. * @Description: 显示带打印店名字的文字
  41. * @Param: [string]
  42. * @return: void
  43. * @Author: 汪泉
  44. * @Date: 2022/6/8 14:35
  45. **/
  46. @Override
  47. public void print(String string) {
  48. System.out.println("===" + name + "===");
  49. System.out.println(string);
  50. }
  51. private void heavyJob(String msg) {
  52. System.out.println(msg);
  53. for (int i = 0; i < 5; i++) {
  54. try {
  55. Thread.sleep(1000);
  56. } catch (InterruptedException e) {
  57. }
  58. System.out.println(".");
  59. }
  60. System.out.println("结束");
  61. }
  62. }

Printable接口:

Printable接口用于是使PrinterProxy类和Printer具有一致性(通过将两个类同时继承这个接口来实现)。setPrinterName方法用于设置打印机的名字;getPrinterName用于获取打印机的名字,print用于显示文字(打印输出)。

  1. public interface Printable {
  2. public abstract void setPrinterName(String name);
  3. public abstract String getPrinterName();
  4. public abstract void print(String string);
  5. }

PrinterProxy类:

PrinterProxy类是扮演“代理人”角色的类。它实现了Printable接口。
name字段中保存了打印店的名字,而real字段中保存的是“本人”。在构造函数中设置打印机的名字(此时还没有生成“本人”)。setPrinterName方法用于设置新的打印机名字。但是当real字段为null时(即还没有生成“本人”),那么只会设置自己的名字。getPrinterName会返回自己的name字段。
Print方法已经超出了代理人的工作范围,因此他会调用realize方法来生成本人。Realize有“实现”的意思。在调用realize方法后,real字段会保存本人也就是(Print类的实例对象),因此可以调用real.print方法。这就是委托。
所以不论set和get方法被调用多少次,都不会生成Printer类的实例。只有当真正需要本人了,才会生成Printer类的实例。(调用者完全不知道是否生成了本人,也不用在意是否生成了本人)。
realize方法很简单,当real字段为null时,它会使用new来生成Printer的实例;如果real字段不为null时,则什么都不做;
这里非常重要的是:

  • Printer类并不知道PrinterProxy类的存在。即,Printer类并不知道自己到底是通过PrinterProxy被调用的还是直接被调用的。
  • 但反过来,PrinterProxy是知道Printer类的。
  • Printer类的setName和realize方法都是synchronized方法。

    1. public class PrinterProxy implements Printable {
    2. private String name; // 名字
    3. private Printer real; // 本人
    4. public PrinterProxy() {
    5. }
    6. /**
    7. * @Description: 构造函数
    8. * @Param: [name]
    9. * @return:
    10. * @Author: 汪泉
    11. * @Date: 2022/6/8 14:47
    12. **/
    13. public PrinterProxy(String name) {
    14. this.name = name;
    15. }
    16. /**
    17. * @Description: 设置名字
    18. * @Param: [name]
    19. * @return: void
    20. * @Author: 汪泉
    21. * @Date: 2022/6/8 14:47
    22. **/
    23. @Override
    24. public synchronized void setPrinterName(String name) {
    25. if (real != null) {
    26. // 同时设置"本人"的名字
    27. real.setPrinterName(name);
    28. }
    29. this.name = name;
    30. }
    31. @Override
    32. public String getPrinterName() {
    33. return name;
    34. }
    35. @Override
    36. public void print(String string) {
    37. realize();
    38. real.print(string);
    39. }
    40. private synchronized void realize() {
    41. if (real == null) {
    42. real = new Printer(name);
    43. }
    44. }
    45. }

    Main类:

    1. public class Main {
    2. public static void main(String[] args) {
    3. PrinterProxy p = new PrinterProxy("Alice");
    4. System.out.println("现在的名字是" + p.getPrinterName() + "。");
    5. p.setPrinterName("Bob");
    6. System.out.println("现在的名字是" + p.getPrinterName() + "。");
    7. p.print("Hello World");
    8. }
    9. }

    输出结果:

    1. 现在的名字是Alice
    2. 现在的名字是Bob
    3. Printer的实例生成中(Bob
    4. .
    5. .
    6. .
    7. .
    8. .
    9. 结束
    10. ===Bob===
    11. Hello World

    Proxy模式中的登场角色:

    Subject(主体):

    Subject定义了使代理角色和主体角色具有一致性的接口。由于存在其Subject角色,所以Client角色不必在意它所使用的究竟是Proxy角色还是RealSubject角色。

    Proxy(代理人):

    Proxy角色会尽量处理来自Client角色的请求。只有当自己不能处理的时候,它才会将工作交给RealSubject角色。Proxy角色只有在必要时才会生成RealSubject角色。Proxy角色实现了在Subject角色中定义的接口。

    RealSubject(实际的主体)

    “本人”RealSubject角色会在“代理人”Proxy角色无法胜任工作时出场。与Proxy角色一样,也实现了在Subject角色中定义的接口。

    Client(请求者):

拓展思路的要点:

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

在Proxy模式中,Proxy角色作为代理人尽力肩负着工作使命。例如,在实例程序中,通过使用Proxy角色,我们成功地将耗时处理(生成实例的处理)推迟到print方法被调用后才进行。
实例程序中的耗时处理消耗的时间并不长,但是假如是在一个大型系统的初始化过程中,存在大量的耗时处理。如果在启动系统时连那些暂时不会被使用的功能也初始化了,那么应用的启动时间将会非常漫长,所以如果只需要使用某个功能时才将其初始化,则可以帮助我们改善用户体验。

有必要划分代理人和本人么:

可以不划分代理人和本人,而是直接在本人类中加入惰性求值功能(即只有必要时才生成实例的功能)。不过,通过划分代理人和本人可以使它们成为独立的组件,在进行修改时也不会互相之间产生影响。(解耦合)
所以划分的方式会更加优雅一些。

代理和委托:

代理人只代理他能解决的问题,当遇到他不能解决的问题时,还是会“转交”给本人去解决。这里的“转交”就是一直提到的委托。

透明性:

PrinterProxy类和Printer类都实现了Printable接口。因此Main类可以完全不必再议调用的究竟是哪个类。

HTTP代理:

缓存和HTTP的思想其实都是代理模式,只要当缓存失效或者页面更新时才会去访问本人。

相关的设计模式:

Adapter模式:

Adapter模式适配了两种具有不同接口的对象,以使它们可以一同工作。而在Proxy模式中,代理人和本人都是相同的。

Decorator模式:

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

练习:

  • 修改PrintProxy,让其不必知道Printer类。

    1. public class PrinterProxy2 implements Printable {
    2. private String name;
    3. private Printable real; // 本人
    4. private String className; // 本人的类名
    5. public PrinterProxy2(String name, String className) {
    6. this.name = name;
    7. this.className = className;
    8. }
    9. @Override
    10. public synchronized void setPrinterName(String name) {
    11. if (real != null) {
    12. real.setPrinterName(name);
    13. }
    14. this.name = name;
    15. }
    16. @Override
    17. public String getPrinterName() {
    18. return name;
    19. }
    20. @Override
    21. public void print(String string) {
    22. realize();
    23. real.print(string);
    24. }
    25. /**
    26. * @Description: 生成本人
    27. * @Param: []
    28. * @return: void
    29. * @Author: 汪泉
    30. * @Date: 2022/6/8 19:59
    31. **/
    32. private synchronized void realize() {
    33. if (real == null) {
    34. try {
    35. real = (Printable) Class.forName(className).newInstance();
    36. real.setPrinterName(name);
    37. } catch (InstantiationException e) {
    38. e.printStackTrace();
    39. } catch (IllegalAccessException e) {
    40. e.printStackTrace();
    41. } catch (ClassNotFoundException e) {
    42. e.printStackTrace();
    43. }
    44. }
    45. }
    46. }

    其实代码中,只是将原本String real替换成了 private Printable real; // 本人 private String className; // 本人的类名,这样做的意义就是进一步解耦了,可以随时更改代理类中的本人,但个人觉得大多数场景下并没有必要。

  • 为什么需要用synchronized修饰setName方法和realize方法:

因为如果不加的枪口下,多个线程分别调用的时候会出现代理类中的name和本人的name不一样的情况发生,所以synchronized可以守护real字段。