查看原文
其他

Bruce Eckel:再聊设计模式(篇二)封装实现

Bruce Eckel 中生代技术
2024-08-23

Bruce Eckel

读完需要

15分钟

速读仅需 2 分钟

布鲁斯 • 埃克尔(Bruce Eckel),C++ 标准委员会的创始成员之一,知名技术顾问,专注于编程语言和软件系统设计方面的研究,常活跃于世界各大顶级技术研讨会。
他自 1986 年以来,累计出版 Thinking in C++、Thinking in Java、On Java 等十余部经典计算机著作,曾多次荣获 Jolt 最佳图书奖(被誉为“软件业界的奥斯卡”),其代表作 Thinking in Java 被译为中文、日文、俄文、意大利文、波兰文、韩文等十几种语言,在世界范围内产生了广泛影响。

接上篇Bruce Eckel:再聊设计模式(篇一)

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工程师是怎么写设计文档的?

聊聊技术人员如何做好团队管理

如何用研发效能搞垮一个团队


继续滑动看下一个
中生代技术
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存