设计模式(6)-避免浪费与类表现

Flyweight

简介

Flyweight的含义是轻量级,该设计模式的作用是让对象变轻。具体来说,当程序中需要大量对象的时候,如果都使用new关键字来分配内存,将会消耗大量的内存空间,而Flyweight模式的核心思想就是“通过尽量共享实例来避免new出实例”。通常我们在使用的时候,会将共享实例使用一个工厂来进行管理,每次需要相应示例的时候,都从工厂中获得。工厂会将这些实例管理在一个pool中,当有请求过来的时候,如果pool中已经存在了对应的实例,则将其返回共享出去,而不用重新创建;如果没有,则新创建一个返回之后,将其管理到pool中,以便后续的共享。但既然是进行了共享,那么就会出现一处修改多处改变的情况,这种时候就需要我们认真考虑对象是否能够进行共享,需要结合具体的场景。

Proxy

简介

Proxy表示代理,也就是代理模式。在代理模式下,真实的功能实现对象会被增加一层代理。具体来说,代理对象会持有一个功能对象,然后向外暴露接口。我们在使用功能的时候,需要使用的是代理对象,而代理对象对应方法的核心功能,则又是由被代理的功能对象来实现的。在代理模式中有如下角色:

  • Subject:该角色定义了功能角色和代理角色需要实现的接口,该接口使得Proxy角色与RealSubject角色具有一致性。在使用的时候,我们不需要在意使用的是哪种角色,因为对于功能的提供来说,两者都是能够完成的
  • Proxy:该角色为代理角色。它会持有一个RealSubject对象,同时会实现Subject接口
  • RealSubject:该角色为实际的功能提供角色

示例程序

在示例程序中,我们会模拟一个打印过程。我们通过一个代理对象PrinterProxy来进行功能调用,该对象实际上调用了真实的Printer对象来实现相关功能。

首先,我们可以声明一个接口表示打印所需的相关功能,这个接口应该同时由代理对象与被代理对象实现。接口Printable内容如下:

1
2
3
4
5
6
7
public interface Printable {
void setPrinterName(String name);

String getPrinterName();

void print(String str);
}

之后,可以完成核心的功能实现对象Printer,它完成的是打印过程的核心功能,同时需要实现上面的接口。这里为了模拟一个复杂耗时的创建操作,在程序中模拟了5s的睡眠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Printer implements Printable {
private String name;

public Printer() {
this.heavyJob("Generating Printer Instance.");
}

public Printer(String name) {
this.name = name;
this.heavyJob("Generating Printer Instance (" + name + ").");
}

private void heavyJob(String message) {
System.out.println(message);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Completed.");
}

@Override
public void setPrinterName(String name) {
this.name = name;
}

@Override
public String getPrinterName() {
return this.name;
}

@Override
public void print(String str) {
System.out.println("=== " + name + " ===");
System.out.println(str);
}
}

完成实际的功能对象之后,就可以完成对应的代理对象PrinterProxy。该对象会持有一个Printer对象,但是并不会完成初始化。只有当真正需要使用到它的时候才会去完成初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class PrinterProxy implements Printable {
private String name;
private Printer real;

public PrinterProxy(String name) {
this.name = name;
}

@Override
public synchronized void setPrinterName(String name) {
if (real != null) {
real.setPrinterName(name);
}
this.name = name;
}

@Override
public String getPrinterName() {
return this.name;
}

@Override
public void print(String str) {
realize();
real.print(str);
}

private synchronized void realize() {
if (real == null) {
real = new Printer(name);
}
}
}

至此就完成了所有的示例代码,我们可以进行如下测试。在测试代码中,我们先给代理对象命名为Alice,但是此时它持有的Printer对象并没有初始化。之后我们改名为Bob,再调用print的时候,此时才进行Printer对象的初始化。

1
2
3
4
5
public void test() {
PrinterProxy proxy = new PrinterProxy("Alice");
proxy.setPrinterName("Bob");
proxy.print("hello world");
}

说明

在示例程序中,我们发现代理对象可以使得实际功能对象的创建延后,在必要的时候才进行创建,可以很好地改善用户体验。当然也可以通过代理模式实现AOP的效果,具体来说就是在代理对象方法中,调用功能对象方法的前后增加相关的逻辑,例如日志的打印和记录等。

在代理模式中,代理对象需要持有一个被代理对象,但是反过来,被代理对象是不知道代理对象的,它并不知道代理对象的存在。

Command

简介

当我们调用一个对象的方法完成相关工作的时候,我们可以从结果的变化中发现工作已经完成,但是并没有办法留下工作的历史记录。而Command就希望解决这种问题,它使用命令对象来表示进行工作这件事,一次工作就对应一个实例。之后再进行相关工作的时候,就不是调用对应对象的方法,而是调用命令对象的execute方法。这样的好处在于我们可以进行工作历史记录的管理,实际上就是管理一个命令对象的集合,同时我们还可以随时重新执行这些命令。而关于命令中应该包含哪些信息,实际上没有绝对的答案,命令的目的不同,应该包含的信息也不同,并且根据保存信息的不同,我们能够重现的粒度也不同。


设计模式(6)-避免浪费与类表现
http://example.com/2023/04/17/设计模式-6-避免浪费与类表现/
作者
EverNorif
发布于
2023年4月17日
许可协议