java实现命令模式
命令模式属于行为设计模式,是Gof设计模式正式列表之一。简言之,命令模式在一个对象中封装执行给定动作的所有数据,包括要执行方法,方法参数以及方法所属对象。通过该模式可以把消费者和命令对象生产者进行解耦,因此也称为生产者-消费者模式。
本文我们学习在java如何实现命令模式,分别通过面向对象和面向函数两种方法,并了解哪些用例可能有用。
面向对象实现
经典实现中,命令模式需要实现四个组件:命令(Command)、接收者(Receiver)、执行器(Invoker)以及客户端(Client)。为了更好理解模式如何工作以及每个组件对应角色,我们通过具体示例进行说明。
假设我们想开发一个文本文件应用程序,需要实现所有执行文本文件相关操作功能,如打开、写、保存等。
所以我们需要把应用分解为上述四个组件。
命令类
命令对象作用是存储执行动作所需的的所有信息,包括要执行的方法、方法参数以及实现方法的对象(接收者)。为了更准确理解命令对象,我们开始开发简单命令接口及其两个实现:
@FunctionalInterface
public interface TextFileOperation {
String execute();
}
public class OpenTextFileOperation implements TextFileOperation {
private final TextFile textFile;
public OpenTextFileOperation(TextFile textFile) {
this.textFile = textFile;
}
@Override
public String execute() {
return textFile.open();
}
}
public class SaveTextFileOperation implements TextFileOperation {
private final TextFile textFile;
public SaveTextFileOperation(TextFile textFile) {
this.textFile = textFile;
}
@Override
public String execute() {
return textFile.save();
}
}
TextFileOperation接口定义了命令对象API,两个实现OpenTextFileOperation 和 SaveTextFileOperation执行具体动作。前者打开文本文件,后者保存文件文件。
命令对象功能很清晰:TextFileOperation命令封装打开和保存文本文件的所有信息,包括接收对象和要调用的方法以及方法参数(本实例没有参数,但实际场景中有可能)。
值得强调的是,执行文件操作的组件是接收者(TextFile实例)。
接收者类
接收者是执行一组内聚操作的对象,是执行实际动作的组件,通过调用命令的excute方法。因此我们定义接收类,是对文本文件对象的建模:
public class TextFile {
private final String name;
public TextFile(String name) {
this.name = name;
}
public String open() {
return "Opening file " + name;
}
public String read() {
return "Reading file " + name;
}
public String write() {
return "Writing to file " + name;
}
public String save() {
return "Saving file " + name;
}
public String copy() {
return "Copying file " + name;
}
public String paste() {
return "Pasting file " + name;
}
}
执行类
执行对象知道如何执行给定命令但不知道命令如何被执行,其仅知道命令接口。在一些场景中,执行对象除了执行,也存储并查询命令,主要为了实现一些额外特性,如录制宏、撤销重做功能。
在我们示例中,很明显必须有一个额外组件负责调用命令对象并通过命令的execute()方法执行它们。这正是执行程序类负责的角色。
请看执行类基本的实现:
public class TextFileOperationExecutor {
private final List<TextFileOperation> textFileOperations = new ArrayList<>();
public String executeOperation(TextFileOperation textFileOperation) {
textFileOperations.add(textFileOperation);
return textFileOperation.execute();
}
}
TextFileOperationExecutor可是抽象层,对命令对象和消费者进行解耦并调用封装在TextFileOperation 命令对象的方法。
同时也存储命令对象至List中,当然这不是命令模式实现必须的,仅仅为了执行过程中增加一些额外的控制。
客户端类
客户端对象是控制命令执行过程,通过指定什么命令要执行,在那个阶段处理执行。因此,为了遵循模式的正式定义,我们定义客户端类,使用main方法:
public static void main(String[] args) {
TextFileOperationExecutor textFileOperationExecutor = new TextFileOperationExecutor();
textFileOperationExecutor.executeOperation( new OpenTextFileOperation(new TextFile("file1.txt"))));
textFileOperationExecutor.executeOperation( new SaveTextFileOperation(new TextFile("file2.txt"))));
}
面向函数实现
到目前为止,我们使用面向对象方法实现命令模式,这非常好。从java8开始,我们可以使用面向函数方法,基于lambda表达式和方法引用,是代码更小、更简洁,避免模版代码。
使用Lambda 表达式
TextFileOperation 接口是函数式接口,我们可以给执行类以lambda形式传递命令对象,则无需显示地创建TextFileOperation 实例:
TextFileOperationExecutor textFileOperationExecutor = new TextFileOperationExecutor();
textFileOperationExecutor.executeOperation(() -> "Opening file file1.txt");
textFileOperationExecutor.executeOperation(() -> "Saving file file1.txt");
现在实现看起来更加精简和简洁,因为我们减少了样板代码的数量。即便如此,问题仍然存在:与面向对象的方法相比,这种方法更好吗? 没有绝对的答案,如果假设在大多数情况下,更紧凑的代码意味着更好的代码,那么事实确实如此。根据经验,我们应该根据每个情况确定何时使用lambda表达式。
使用方法引用
类似的,我们也可以使用方法引用给执行器传递命令对象:
TextFileOperationExecutor textFileOperationExecutor
= new TextFileOperationExecutor();
TextFile textFile = new TextFile("file1.txt");
textFileOperationExecutor.executeOperation(textFile::open);
textFileOperationExecutor.executeOperation(textFile::save);
在本例中,实现比使用lambdas的实现稍微复杂点,因为仍然需要创建TextFile实例。
总结
本文我们学习了命令模式的关键概念,以及如何在java通过面向对象和面向函数进行实现。
本文参考链接:https://blog.csdn.net/neweastsun/article/details/88372541