The Command Pattern in Java – Java中的命令模式

最后修改: 2018年 5月 10日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The command pattern is a behavioral design pattern and is part of the GoF‘s formal list of design patterns. Simply put, the pattern intends to encapsulate in an object all the data required for performing a given action (command), including what method to call, the method’s arguments, and the object to which the method belongs.

命令模式是一种行为设计模式,是GoF的正式设计模式列表的一部分。简单地说,该模式旨在将执行特定动作(命令)所需的所有数据封装在一个对象中,包括调用什么方法、该方法的参数以及该方法所属的对象。

This model allows us to decouple objects that produce the commands from their consumers, so that’s why the pattern is commonly known as the producer-consumer pattern.

这种模式允许我们将产生命令的对象与它们的消费者脱钩,所以这就是为什么该模式通常被称为生产者-消费者模式。

In this tutorial, we’ll learn how to implement the command pattern in Java by using both object-oriented and object-functional approaches, and we’ll see in what use cases it can be useful.

在本教程中,我们将学习如何通过使用面向对象和对象功能的方法在Java中实现命令模式,并且我们将看到它在哪些用例中可以发挥作用。

2. Object-Oriented Implementation

2.面向对象的实现

In a classic implementation, the command pattern requires implementing four components: the Command, the Receiver, the Invoker, and the Client.

在一个经典的实现中,命令模式需要实现四个组件:命令、接收者、调用者和客户端

To understand how the pattern works and the role that each component plays, let’s create a basic example.

为了理解该模式是如何工作的,以及每个组件发挥的作用,让我们创建一个基本的例子。

Let’s suppose that we want to develop a text file application. In such a case, we should implement all functionality required for performing some text-file related operations, such as opening, writing, saving a text file, and so forth.

让我们假设我们想开发一个文本文件应用程序。在这种情况下,我们应该实现执行一些与文本文件有关的操作所需的所有功能,如打开、写入、保存一个文本文件等。

So, we should break down the application into the four components mentioned above.

因此,我们应该将应用程序分解为上述四个部分。

2.1. Command Classes

2.1.命令类

A command is an object whose role is to store all the information required for executing an action, including the method to call, the method arguments, and the object (known as the receiver) that implements the method.

命令是一个对象,其作用是存储执行一个动作所需的所有信息,包括要调用的方法、方法参数和实现该方法的对象(称为接收器)。

To get a more accurate idea of how command objects work, let’s start developing a simple command layer which includes just one single interface and two implementations:

为了更准确地了解命令对象的工作原理,让我们开始开发一个简单的命令层,它只包括一个单一的接口和两个实现。

@FunctionalInterface
public interface TextFileOperation {
    String execute();
}
public class OpenTextFileOperation implements TextFileOperation {

    private TextFile textFile;
    
    // constructors
    
    @Override
    public String execute() {
        return textFile.open();
    }
}
public class SaveTextFileOperation implements TextFileOperation {
    
    // same field and constructor as above
        
    @Override
    public String execute() {
        return textFile.save();
    }
}

In this case, the TextFileOperation interface defines the command objects’ API, and the two implementations, OpenTextFileOperation and SaveTextFileOperation, perform the concrete actions. The former opens a text file, while the latter saves a text file.

在这种情况下,TextFileOperation接口定义了命令对象的API,而两个实现,OpenTextFileOperationSaveTextFileOperation,执行具体的操作。前者打开了一个文本文件,而后者则保存了一个文本文件。

It’s clear to see the functionality of a command object: the TextFileOperation commands encapsulate all the information required for opening and saving a text file, including the receiver object, the methods to call, and the arguments (in this case, no arguments are required, but they could be).

可以清楚地看到命令对象的功能:TextFileOperation命令封装了打开和保存文本文件所需的所有信息,包括接收器对象、要调用的方法和参数(在这种情况下,不需要参数,但可以是参数)。

It’s worth stressing that the component that performs the file operations is the receiver (the TextFile instance).

值得强调的是,执行文件操作的组件是接收器(TextFile实例)

2.2. The Receiver Class

2.2.接收者类

A receiver is an object that performs a set of cohesive actions. It’s the component that performs the actual action when the command’s execute() method is called.

接收器是一个执行一组凝聚性动作的对象。当命令的 execute()方法被调用时,它是执行实际行动的组件。

In this case, we need to define a receiver class, whose role is to model TextFile objects:

在这种情况下,我们需要定义一个接收器类,其作用是为TextFile对象建模。

public class TextFile {
    
    private String name;
    
    // constructor
    
    public String open() {
        return "Opening file " + name;
    }
    
    public String save() {  
        return "Saving file " + name;
    }
    
    // additional text file methods (editing, writing, copying, pasting)
}

2.3. The Invoker Class

2.3.调用者类

An invoker is an object that knows how to execute a given command but doesn’t know how the command has been implemented. It only knows the command’s interface.

一个调用者是一个对象,它知道如何执行一个给定的命令,但不知道该命令是如何被实现的。

In some cases, the invoker also stores and queues commands, aside from executing them. This is useful for implementing some additional features, such as macro recording or undo and redo functionality.

在某些情况下,调用者除了执行命令外,还存储和排队。这对于实现一些额外的功能是很有用的,如宏记录或撤销和重做功能。

In our example, it becomes evident that there must be an additional component responsible for invoking the command objects and executing them through the commands’ execute() method. This is exactly where the invoker class comes into play.

在我们的例子中,很明显,必须有一个额外的组件负责调用命令对象,并通过命令的execute()方法来执行它们。这正是调用者类发挥作用的地方

Let’s look at a basic implementation of our invoker:

让我们看看我们的调用器的基本实现。

public class TextFileOperationExecutor {
    
    private final List<TextFileOperation> textFileOperations
     = new ArrayList<>();
    
    public String executeOperation(TextFileOperation textFileOperation) {
        textFileOperations.add(textFileOperation);
        return textFileOperation.execute();
    }
}

The TextFileOperationExecutor class is just a thin layer of abstraction that decouples the command objects from their consumers and calls the method encapsulated within the TextFileOperation command objects.

TextFileOperationExecutor类只是一个薄薄的抽象层,它将命令对象与消费者解耦,并调用TextFileOperation命令对象中封装的方法。

In this case, the class also stores the command objects in a List. Of course, this is not mandatory in the pattern implementation, unless we need to add some further control to the operations’ execution process.

在这种情况下,该类还将命令对象存储在一个List中。当然,这在模式实现中不是强制性的,除非我们需要对操作的执行过程添加一些进一步的控制。

2.4. The Client Class

2.4.客户端类

A client is an object that controls the command execution process by specifying what commands to execute and at what stages of the process to execute them.

客户端是一个控制命令执行过程的对象,它指定要执行哪些命令以及在过程的哪个阶段执行这些命令。

So, if we want to be orthodox with the pattern’s formal definition, we must create a client class by using the typical main method:

因此,如果我们想正统地使用该模式的正式定义,我们必须通过使用典型的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"))));
}

3. Object-Functional Implementation

3.对象-功能的实现

So far, we’ve used an object-oriented approach to implement the command pattern, which is all well and good.

到目前为止,我们已经用面向对象的方法来实现命令模式,这一切都很好。

From Java 8, we can use an object-functional approach, based on lambda expressions and method references, to make the code a little bit more compact and less verbose.

从Java 8开始,我们可以使用基于lambda表达式和方法引用的对象功能方法,以使代码更紧凑、更不啰嗦

3.1. Using Lambda Expressions

3.1.使用Lambda表达式

As the TextFileOperation interface is a functional interface, we can pass command objects in the form of lambda expressions to the invoker, without having to create the TextFileOperation instances explicitly:

由于TextFileOperation接口是一个函数接口,我们可以以lambda表达式的形式将命令对象传递给调用者,而不必明确创建TextFileOperation实例。

TextFileOperationExecutor textFileOperationExecutor
 = new TextFileOperationExecutor();
textFileOperationExecutor.executeOperation(() -> "Opening file file1.txt");
textFileOperationExecutor.executeOperation(() -> "Saving file file1.txt");

The implementation now looks much more streamlined and concise, as we’ve reduced the amount of boilerplate code.

现在的实现看起来更加精简,因为我们已经减少了模板代码的数量

Even so, the question still stands: is this approach better, compared to the object-oriented one?

即便如此,问题仍然存在:与面向对象的方法相比,这种方法是否更好?

Well, that’s tricky. If we assume that more compact code means better code in most cases, then indeed it is.

嗯,这很棘手。如果我们假设更紧凑的代码在大多数情况下意味着更好的代码,那么确实如此。

As a rule of thumb, we should evaluate on a per-use-case basis when to resort to lambda expressions.

作为一条经验法则,我们应该根据每一个使用情况来评估什么时候需要使用lambda表达式

3.2. Using Method References

3.2.使用方法引用

Similarly, we can use method references for passing command objects to the invoker:

同样地,我们可以使用方法引用来实现将命令对象传递给调用者:

TextFileOperationExecutor textFileOperationExecutor
 = new TextFileOperationExecutor();
TextFile textFile = new TextFile("file1.txt");
textFileOperationExecutor.executeOperation(textFile::open);
textFileOperationExecutor.executeOperation(textFile::save);

In this case, the implementation is a little bit more verbose than the one that uses lambdas, as we still had to create the TextFile instances.

在这种情况下,实现比使用lambdas的更繁琐一些,因为我们仍然不得不创建TextFile实例。

4. Conclusion

4.结论

In this article, we learned the command pattern’s key concepts and how to implement the pattern in Java by using an object-oriented approach and a combination of lambda expressions and method references.

在这篇文章中,我们学习了命令模式的关键概念,以及如何通过使用面向对象的方法以及lambda表达式和方法引用的组合在Java中实现该模式。

As usual, all the code examples shown in this tutorial are available over on GitHub.

像往常一样,本教程中显示的所有代码示例都可以在GitHub上获得