1,免责声明,本文大部分内容摘自《Java8函数式编程》。在这本书的基础上,根据自己的理解和网上一些博文,精简或者修改。本次分享的内容,只用于技术分享,不作为任何商业用途。当然这本书是非常值得一读,强烈建议买一本!
2,本次分享的样例代码均上传到github上,请点击这里。
注意:本章所有的例子大多围绕 1.3 节介绍的案例展开(音乐)。
Lambda 表达式是对 Java 语言的一点简单改进,在 JDK 标准类库中,运行它的方式各种各样。但是大多数 Java 代码都不是由开发 JDK 的程序员写的, 而是像你我这样的普通程序员。为了最大限度发挥 Lambda 表达式的优势,大家需要将其引入已有代码中。作为一名职业 Java 程序员,Lambda 表达式没有什么特别的,和接口、类一样,它只是程序员工具箱中的一件新工具。
本章将探索如何使用 Lambda 表达式实现 SOLID原则,该原则是开发良好面向对象程序的准则。使用 Lambda 表达式,还能改进一些现有的设计模式,本章也会为大家简要介绍几个这样的例子。
本章旨在帮助大家写出优秀的程序,会给出一些良好的设计原则和模式,在此基础之上,就能开发出可维护且十分可靠的程序。我们不光会用到 JDK 提供的崭新类库,而且会教大家如何在自己的领域和应用程序中使用 Lambda 表达式。
主要内容如下:
- 8.1 Lambda 表达式改变了设计模式
- 8.2 使用 Lambda 表达式的领域专用语言
- 8.3 使用 Lambda 表达式的 SOLID 原则
- 8.4 进阶阅读
8.1 Lambda 表达式改变了设计模式
设计模式是一种设计思想,它是软件架构中解决通用问题的模板。如果碰到一个问题,并且恰好熟悉一个与之适应的模式,就能直接应用该模式来解决问题。从某种程度上来说,设计模式将解决特定问题的最佳实践途径固定了下来。
本章我们讨论的是如何使用 Lambda 表达式,让现有设计模式变得更好、更简单。Java 8 新特性是所有这些设计模式变化的推动因素。
8.1.1 命令者模式
命令者是一个对象,它封装了调用另一个方法的所有细节,命令者模式使用该对象,可以编写出根据运行期条件,顺序调用方法的一般化代码。命令者模式中有四个类参与其中,如图 8-1 所示。
// 图 8-1:命令者模式
- 命令接收者 -> 执行实际任务。
- 命令者 -> 封装了所有调用命令执行者的信息。
- 发起者 -> 控制一个或多个命令的顺序和执行。
- 客户端 -> 创建具体的命令者实例。
看一个命令者模式的具体例子,看看如何使用 Lambda 表达式改进该模式。假设有一个 GUI Editor组件,在上面可以执行 open、save 等一系列操作,如:例 8-1 所示。现在我们想实现 宏功能 ,也就是说,可以将一系列操作录制下来,以后作为一个操作执行,这就是我们的命令接收者。
// 例 8-1 文本编辑器可能具有的一般功能
public interface Editor {
public void save();
public void open();
public void close();
}
在 Editor
例子中,像 open
、save
这样的操作称为 命令,我们需要一个统一的接口来概括这些不同的操作,我将这个接口叫作 Action
,它代表了一个操作。所有的命令都要实现该接口 (例 8-2)。
// 例 8-2 所有操作均实现 Action 接口
public interface Action {
public void perform();
}
现在让每个操作都实现该接口,这些类要做的只是在 Action
接口中调用 Editor
类中的一个方法。遵循恰当的命名规范,用类名代表操作,比如 save
方法对应 Save
类。例 8-3 和例 8-4 是定义好的命令对象。
// 例 8-3 save 操作代理给 Editor 方法
public class Save implements Action {
private final Editor editor;
public Save(Editor editor) {
this.editor = editor;
}
@Override
public void perform() {
editor.save();
}
}
// 例 8-4 open 操作代理给 Editor 方法
public class Open implements Action {
private final Editor editor;
public Open(Editor editor) {
this.editor = editor;
}
@Override
public void perform() {
editor.open();
}
}
现在可以实现宏类 Macro
了,然后一起运行。我们使用 List
保存操作序列,然后调用 forEach
方法按顺序执行每一个 Action
,例 8-5 就是我们的命令发起者。
// 例 8-5 包含操作序列的宏,可按顺序执行操作
public class Macro {
private final List<Action> actions;
public Macro() {
actions = new ArrayList<>();
}
public void record(Action action) {
actions.add(action);
}
public void run() {
actions.forEach(Action::perform);
}
}
在构建宏时,将每一个命令实例加入 Macro
对象的列表,然后运行宏,就会按顺序执行每一条命令。例 8-6 展示了如何在用户代码中使用 Macro
对象。
// 例 8-6 使用命令者模式构建宏
Macro macro = new Macro();
macro.record(new Open(editor));
macro.record(new Save(editor));
macro.record(new Close(editor));
macro.run();
Lambda 表达式能做点什么呢?事实上,所有的命令类,Save
、Open
都是 Lambda 表达式, 只是暂时藏在类的外壳下。它们是一些行为,我们通过创建类将它们在对象之间传递。Lambda 表达式特性可以让我们扔掉这些类,如:例 8-7。
// 例 8-7 使用 Lambda 表达式构建宏
Macro macro = new Macro();
macro.record(() -> editor.open());
macro.record(() -> editor.save());
macro.record(() -> editor.close());
macro.run();
事实上,如果意识到这些 Lambda 表达式的作用只是调用了一个方法,使用方法引用还能让问题变得更简单 (如例 8-8 所示)。
// 例 8-8 使用方法引用构建宏
Macro macro = new Macro();
macro.record(editor::open);
macro.record(editor::save);
macro.record(editor::close);
macro.run();
8.1.2 策略模式
策略模式 能在运行时改变软件的算法行为。其主要思想是定义一个通用的问题,使用不同的算法来实现,然后将这些算法都封装在一个统一接口的背后。
文件压缩就是一个很好的例子。压缩文件的方式可以使用 zip
算法,也可以使用 gzip
算法,我们实现一个通用的 Compressor
类,能用任何一种算法压缩文件。
首先,为我们的策略定义 API (参见图 8-2),把它叫作 CompressionStrategy
,每一种文件压缩算法都要实现该接口。该接口有一个 compress
方法,接受并返回一个 OutputStream
对象,返回的就是压缩后的 OutputStream
(如例 8-9 所示)。
// 图 8-2:策略模式
// 例 8-9 定义压缩数据的策略接口
public interface CompressionStrategy {
public OutputStream compress(OutputStream data) throws IOException;
}
有两个类实现了该接口,分别代表 gzip
和 zip
算法,使用 Java 内置的类实现 gzip
和 zip
算法 (例 8-10、8-11)。
// 例 8-10 使用 gzip 算法压缩数据
public class GzipCompressionStrategy implements CompressionStrategy {
@Override
public OutputStream compress(OutputStream data) throws IOException {
return new GZIPOutputStream(data);
}
}
// 例 8-11 使用 zip 算法压缩数据
public class ZipCompressionStrategy implements CompressionStrategy {
@Override
public OutputStream compress(OutputStream data) throws IOException {
return new ZipOutputStream(data);
}
}
现在可以动手实现 Compressor
类了,这里就是使用策略模式的地方。该类有一个 compress
方法,读入文件,压缩后输出。它的构造函数有一个 CompressionStrategy
参数,调用代码可以在运行期使用该参数决定使用哪种压缩策略,比如,可以等待用户输入选择(如例 8-12 所示)。
// 例 8-12 在构造类时提供压缩策略</