代理模式,指的是代替别人进行工作的人。当不一定需要本人亲自进行工作时,就可以寻找代理人去完成工作。但代理人毕竟只是代理人,能代替本人做的事情终究是有限的。因此,当代理人遇到无法自己解决的事情时就回去找本人解决该问题。
示例程序:
| 名字 | 说明 |
|---|---|
| Printer | 表示带名字的打印机的类(本人) |
| Printable | Printer和PrinterProxy的共同接口 |
| PrinterProxy | 表示带名字的打印机的类(代理人) |
| Main | 测试程序行为的类 |
Printer类:
Printer类是表示“本人”的类。在构造函数中,去做一些所谓的“重活”。Proxy模式的核心是PrinterProxy类。Printer类自身并不难理解。
public class Printer implements Printable {private String name;public Printer() {heavyJob("Printer的实例生成中");}/*** @Description: 构造函数* @Param: [name]* @return:* @Author: 汪泉* @Date: 2022/6/8 14:34**/public Printer(String name) {this.name = name;heavyJob("Printer的实例生成中(" + name + ")");}/*** @Description: 设置名字* @Param: [name]* @return: void* @Author: 汪泉* @Date: 2022/6/8 14:35**/@Overridepublic void setPrinterName(String name) {this.name = name;}/*** @Description: 获取名字* @Param: []* @return: java.lang.String* @Author: 汪泉* @Date: 2022/6/8 14:35**/@Overridepublic String getPrinterName() {return name;}/*** @Description: 显示带打印店名字的文字* @Param: [string]* @return: void* @Author: 汪泉* @Date: 2022/6/8 14:35**/@Overridepublic void print(String string) {System.out.println("===" + name + "===");System.out.println(string);}private void heavyJob(String msg) {System.out.println(msg);for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {}System.out.println(".");}System.out.println("结束");}}
Printable接口:
Printable接口用于是使PrinterProxy类和Printer具有一致性(通过将两个类同时继承这个接口来实现)。setPrinterName方法用于设置打印机的名字;getPrinterName用于获取打印机的名字,print用于显示文字(打印输出)。
public interface Printable {public abstract void setPrinterName(String name);public abstract String getPrinterName();public abstract void print(String string);}
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方法。
public class PrinterProxy implements Printable {private String name; // 名字private Printer real; // 本人public PrinterProxy() {}/*** @Description: 构造函数* @Param: [name]* @return:* @Author: 汪泉* @Date: 2022/6/8 14:47**/public PrinterProxy(String name) {this.name = name;}/*** @Description: 设置名字* @Param: [name]* @return: void* @Author: 汪泉* @Date: 2022/6/8 14:47**/@Overridepublic synchronized void setPrinterName(String name) {if (real != null) {// 同时设置"本人"的名字real.setPrinterName(name);}this.name = name;}@Overridepublic String getPrinterName() {return name;}@Overridepublic void print(String string) {realize();real.print(string);}private synchronized void realize() {if (real == null) {real = new Printer(name);}}}
Main类:
public class Main {public static void main(String[] args) {PrinterProxy p = new PrinterProxy("Alice");System.out.println("现在的名字是" + p.getPrinterName() + "。");p.setPrinterName("Bob");System.out.println("现在的名字是" + p.getPrinterName() + "。");p.print("Hello World");}}
输出结果:
现在的名字是Alice。现在的名字是Bob。Printer的实例生成中(Bob).....结束===Bob===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类。
public class PrinterProxy2 implements Printable {private String name;private Printable real; // 本人private String className; // 本人的类名public PrinterProxy2(String name, String className) {this.name = name;this.className = className;}@Overridepublic synchronized void setPrinterName(String name) {if (real != null) {real.setPrinterName(name);}this.name = name;}@Overridepublic String getPrinterName() {return name;}@Overridepublic void print(String string) {realize();real.print(string);}/*** @Description: 生成本人* @Param: []* @return: void* @Author: 汪泉* @Date: 2022/6/8 19:59**/private synchronized void realize() {if (real == null) {try {real = (Printable) Class.forName(className).newInstance();real.setPrinterName(name);} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}}}
其实代码中,只是将原本String real替换成了 private Printable real; // 本人 private String className; // 本人的类名,这样做的意义就是进一步解耦了,可以随时更改代理类中的本人,但个人觉得大多数场景下并没有必要。
为什么需要用synchronized修饰setName方法和realize方法:
因为如果不加的枪口下,多个线程分别调用的时候会出现代理类中的name和本人的name不一样的情况发生,所以synchronized可以守护real字段。
