Bruce Eckel:再聊设计模式(篇二)封装实现
Bruce Eckel
读完需要
15分钟速读仅需 2 分钟
布鲁斯 • 埃克尔(Bruce Eckel),C++ 标准委员会的创始成员之一,知名技术顾问,专注于编程语言和软件系统设计方面的研究,常活跃于世界各大顶级技术研讨会。
他自 1986 年以来,累计出版 Thinking in C++、Thinking in Java、On Java 等十余部经典计算机著作,曾多次荣获 Jolt 最佳图书奖(被誉为“软件业界的奥斯卡”),其代表作 Thinking in Java 被译为中文、日文、俄文、意大利文、波兰文、韩文等十几种语言,在世界范围内产生了广泛影响。
5
封装实现
设计模式中有种常见的结构,即引入一个代理 类,该代理类“封装”了实际执行工作的实现类。当在代理类中调用一个方法时,它会转而调用实现类中的某个方法。接下来会具体探讨 3 种使用了这种结构的模式:代理模式(Proxy)、状态模式(State)以及状态机(State Machine)。
代理类伴随着提供具体实现的一个或多个类,从基类中派生而来(见图 8-1):
interface:接口
Surrogate:代理类
Implementation 1:实现 1
Implementation 2:实现 2
Etc.:其他实现
Implementation:实现
代理对象会连接到具体实现,它会在连接处转发所有的方法调用。
surrogate,替代、代理的意思,和本章其他地方提到的 proxy(代理模式)不是同一个词,但意思相近。——译者注
在结构上,代理模式只有一个实现,而状态模式有多个实现。(在《设计模式》中)一般认为这两个模式的应用应该是各不相同的:代理模式用来控制对自身实现的访问,而状态模式则可以让我们动态地改变实现。不过,如果将“对实现的访问控制”的概念延伸一下,那么这两者其实可以很好地结合在一起。
5.1
代理模式
如果根据图 8-1 来实现代理模式,看起来应该是这样:
1// patterns/ProxyDemo.java
2// 代理模式的基本示例
3
4interface ProxyBase {
5 void f();
6 void g();
7 void h();
8}
9
10class Proxy implements ProxyBase {
11 private ProxyBase implementation =
12 new Implementation();
13 // 将方法调用传递给实现:
14 @Override
15 public void f() { implementation.f(); }
16 @Override
17 public void g() { implementation.g(); }
18 @Override
19 public void h() { implementation.h(); }
20}
21
22class Implementation implements ProxyBase {
23 @Override public void f() {
24 System.out.println("Implementation.f()");
25 }
26 @Override public void g() {
27 System.out.println("Implementation.g()");
28 }
29 @Override public void h() {
30 System.out.println("Implementation.h()");
31 }
32}
33
34public class ProxyDemo {
35 public static void main(String[] args) {
36 Proxy p = new Proxy();
37 p.f();
38 p.g();
39 p.h();
40 }
41}
42/* 输出:
43Implementation.f()
44Implementation.g()
45Implementation.h()
46*/
Implementation 并不需要和 Proxy 拥有相同的接口。只要 Proxy 能以某种方式“代言”实现类,就能符合基本的思路。不过,如果能有一个公共的接口,以强制 Implementation 必须实现 Proxy 需要调用的所有方法,会更方便。
5.2
状态模式
状态模式在代理模式的基础上增加了更多的实现,以及在代理类的生命周期内切换实现的方法。下面是一种状态模式的实现:
1// patterns/StateDemo.java
2// 基本的状态模式示例
3
4class State {
5 private State implementation;
6 protected State() {}
7 public State(State imp) {
8 implementation = imp;
9 }
10 public void change(State newImp) {
11 implementation = newImp;
12 }
13 //将方法调用转发到实现类:
14 public void f() { implementation.f(); }
15 public void g() { implementation.g(); }
16 public void h() { implementation.h(); }
17}
18
19class Implementation1 extends State {
20 @Override public void f() {
21 System.out.println("Implementation1.f()");
22 }
23 @Override public void g() {
24 System.out.println("Implementation1.g()");
25 }
26 @Override public void h() {
27 System.out.println("Implementation1.h()");
28 }
29}
30
31class Implementation2 extends State {
32 @Override public void f() {
33 System.out.println("Implementation2.f()");
34 }
35 @Override public void g() {
36 System.out.println("Implementation2.g()");
37 }
38 @Override public void h() {
39 System.out.println("Implementation2.h()");
40 }
41}
42
43public class StateDemo {
44 static void test(State s) {
45 s.f();
46 s.g();
47 s.h();
48 }
49 public static void main(String[] args) {
50 State s = new State(new Implementation1());
51 test(s);
52 System.out.println("Changing implementation");
53 s.change(new Implementation2());
54 test(s);
55 }
56}
57/* 输出:
58Implementation1.f()
59Implementation1.g()
60Implementation1.h()
61Changing implementation
62Implementation2.f()
63Implementation2.g()
64Implementation2.h()
65*/
main()方法一开始用的是第一个实现(类),然后切换到了第二种。Implementation1和Implementation2都继承自State,但这并不是必需的。只要你实现了一个对象——它可以被转发调用给成员对象,并且该成员对象可以被动态地替换——就可以认为你在使用状态模式。下面是第二个例子,其中State控制器对象和调用转发的目标对象并不是相同的类型:
1// patterns/CleanTheFloor.java
2// State with different compositional interface.
3// 由不同类型的接口组成的状态(模式)
4
5class FloorCleaner {
6 private Attachment attachment = new Vacuum();
7 public void change(Attachment newAttachment) {
8 attachment = newAttachment;
9 }
10 public void clean() { attachment.action(); }
11}
12
13interface Attachment {
14 void action();
15}
16
17class Vacuum implements Attachment {
18 @Override public void action() {
19 System.out.println("Vacuuming");
20 }
21}
22
23class Mop implements Attachment {
24 @Override public void action() {
25 System.out.println("Mopping");
26 }
27}
28
29public class CleanTheFloor {
30 public static void main(String[] args) {
31 FloorCleaner floorCleaner = new FloorCleaner();
32 floorCleaner.clean();
33 floorCleaner.change(new Mop());
34 floorCleaner.clean();
35 }
36}
37/* 输出:
38Vacuuming
39Mopping
40*/
代理模式和状态模式的不同之处在于要解决何种问题。《设计模式》中,代理模式常见的应用场景描述如下。远程代理(remote proxy)。它用来代理处于不同地址空间的对象。例如,Java 远程方法调用(RMI)编译器 rmic 可以自动创建一个远程代理。
虚拟代理(virtual proxy)。提供了“延迟加载”的能力,可以按需创建开销较大的对象。如果永远不需要创建该对象,那么就无须花费相应的开销。可以等到需要的时候再初始化该对象,以减少启动耗时。
保护代理(protection proxy)。在你并不希望赋予调用方程序员访问被代理对象的完整权限时使用。
智能引用(smart reference)。在访问被代理的对象时执行额外的操作,例如:
持续跟踪某个特定对象持有的引用数;
实现写时复制(copy-on-write),以防止对象引用别名(减少系统开销和空间占用);
对指定方法的调用进行计数。
可以将 Java 的引用看作某种保护性代理,因为其控制了在堆上对实际对象的访问(并且保证了——比如说——你不会在 null 引用上调用方法)。
在《设计模式》中,代理模式和状态模式看起来彼此并无关联。正如我们所看到的,状态模式可以使用独立的实现层。类似地,代理模式不需要为了自身的实现而使用相同的基类,只需要代理(proxy)对象是其他对象的代理(surrogate)。抛开细节不论,在代理模式和状态模式中,都存在一个代理(surrogate)来将方法调用传递给某个实现对象。
5.3
状态机模式
这种模式在本书第 1 章中也出现过。
状态模式使得调用方程序员可以改变具体的实现,而状态机模式则通过一种强加的结构来自动改变具体实现。具体的实现表示系统的当前状态。在不同的状态下,系统有着不同的行为,这是因为它的内部包含状态模式。
将系统从一种状态改变为另一种状态的代码通常使用了模板方法,如下例所示:
1// patterns/state/StateMachineDemo.java
2// 状态机模式
3// {java patterns.state.StateMachineDemo}
4package patterns.state;
5import java.util.*;
6import onjava.Nap;
7
8interface State {
9 void run();
10}
11
12abstract class StateMachine {
13 protected State currentState;
14 protected abstract boolean changeState();
15 // 模板方法:
16 protected final void runAll() {
17 while(changeState())
18 currentState.run();
19 }
20}
21
22// 为每种状态实现一个不同的子类:
23class Wash implements State {
24 @Override public void run() {
25 System.out.println("Washing");
26 new Nap(0.5);
27 }
28}
29
30class Spin implements State {
31 @Override public void run() {
32 System.out.println("Spinning");
33 new Nap(0.5);
34 }
35}
36
37class Rinse implements State {
38 @Override public void run() {
39 System.out.println("Rinsing");
40 new Nap(0.5);
41 }
42}
43
44class Washer extends StateMachine {
45 private int i = 0;
46 private Iterator<State> states =
47 Arrays.asList(
48 new Wash(), new Spin(),
49 new Rinse(), new Spin()
50 ).iterator();
51 Washer() { runAll(); }
52 @Override public boolean changeState() {
53 if(!states.hasNext())
54 return false;
55 // 将代理引用指向一个新的State对象:
56 currentState = states.next();
57 return true;
58 }
59}
60
61public class StateMachineDemo {
62 public static void main(String[] args) {
63 new Washer();
64 }
65}
66/* 输出:
67Washing
68Spinning
69Rinsing
70Spinning
71*/
此处 StateMachine 控制着当前状态,并决定下一个状态是什么。不过状态对象自身可能也会决定下一个状态,一般是基于某种对系统的输入。这通常是一种更灵活的办法。(未完待续:工厂模式:封装对象的创建)
本书特色
查漏宝典:涵盖Java关键特性的设计原理和应用方法
避坑指南:以产业实践的得失为鉴,指明Java开发者不可不知的设计陷阱
经典普适:值得不同层次的Java开发者反复研读
专家领读:4位一线业务专家、知名作译者帮你拆解书中难点,总结Java开发精要
值得一提的是,为了帮助新手加深理解,出版方邀请了4位从业10年以上知名作译者(DDD 专家张逸、服务端专家梁桂钊、软件系统架构专家王前明、译者陈德伟)为本书录制【精讲视频】和【导读指南】,该视频已在B站和图灵社区发布,感兴趣的朋友可以去看看。
往期推荐
Bruce Eckel:再聊设计模式(篇一)
Bruce Eckel - 详解函数式编程(卷一)
Bruce Eckel - 详解函数式编程(卷二)
Bruce Eckel - 详解函数式编程(卷三)
聊聊 8 种架构模式
如何在 SpringBoot 项目中控制 RocketMQ消费线程数量
同事多线程使用不当导致OOM,被我怒怼了
Google工程师是怎么写设计文档的?
聊聊技术人员如何做好团队管理
如何用研发效能搞垮一个团队