State Design Pattern in Java – Java中的状态设计模式

最后修改: 2018年 8月 12日

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

1. Overview

1.概述

In this tutorial, we’ll introduce one of the behavioral GoF design patterns – the State pattern.

在本教程中,我们将介绍GoF的行为设计模式之一–状态模式。

At first, we’ll give an overview of its purpose and explain the problem it tries to solve. Then, we’ll have a look at the State’s UML diagram and implementation of the practical example.

首先,我们将概述它的目的并解释它试图解决的问题。然后,我们将看一下国家的UML图和实际例子的实现。

2. State Design Pattern

2.状态设计模式

The main idea of State pattern is to allow the object for changing its behavior without changing its class. Also, by implementing it, the code should remain cleaner without many if/else statements.

状态模式的主要理念是:允许对象在不改变其类的情况下改变其行为。此外,通过实施它,代码应该保持清洁,不需要很多if/else语句。

Imagine we have a package which is sent to a post office, the package itself can be ordered, then delivered to a post office and finally received by a client. Now, depending on the actual state, we want to print its delivery status.

想象一下,我们有一个寄往邮局的包裹,包裹本身可以被订购,然后被送到邮局,最后被客户收到。现在,根据实际状态,我们想打印它的交付状态。

The simplest approach would be to add some boolean flags and apply simple if/else statements within each of our methods in the class. That won’t complicate it much in a simple scenario. However, it might complicate and pollute our code when we’ll get more states to process which will result in even more if/else statements.

最简单的方法是在类中的每个方法中添加一些布尔标志和应用简单的if/else语句。在一个简单的情况下,这不会使它变得很复杂。然而,当我们有更多的状态需要处理时,它可能会使我们的代码复杂化,并污染我们的代码,这将导致更多的if/else语句。

Besides, all logic for each of the states would be spread across all methods. Now, this is where the State pattern might be considered to use. Thanks to the State design pattern, we can encapsulate the logic in dedicated classes, apply the Single Responsibility Principle and Open/Closed Principle, have cleaner and more maintainable code.

此外,每个状态的所有逻辑都会分布在所有的方法中。现在,这就是可以考虑使用状态模式的地方。由于采用了状态设计模式,我们可以将逻辑封装在专门的类中,应用单一责任原则开放/关闭原则,拥有更干净和更可维护的代码。

3. UML Diagram

3.UML图示

UML diagram of state design pattern

In the UML diagram, we see that Context class has an associated State which is going to change during program execution.

在UML图中,我们看到Context类有一个相关的State,它将在程序执行期间发生变化。

Our context is going to delegate the behavior to the state implementation. In other words, all incoming requests will be handled by the concrete implementation of the state.

我们的上下文将把行为委托给状态实现。换句话说,所有传入的请求将由状态的具体实现来处理。

We see that logic is separated and adding new states is simple – it comes down to adding another State implementation if needed.

我们看到,逻辑是分离的,添加新的状态也很简单–如果需要的话,归根结底就是添加另一个State实现。

4. Implementation

4.实施

Let’s design our application. As already mentioned, the package can be ordered, delivered and received, therefore we’re going to have three states and the context class.

让我们来设计我们的应用程序。正如已经提到的,包裹可以被订购、交付和接收,因此我们要有三个状态和上下文类。

First, let’s define our context, that’s going to be a Package class:

首先,让我们定义我们的上下文,这将是一个Package类。

public class Package {

    private PackageState state = new OrderedState();

    // getter, setter

    public void previousState() {
        state.prev(this);
    }

    public void nextState() {
        state.next(this);
    }

    public void printStatus() {
        state.printStatus();
    }
}

As we can see, it contains a reference for managing the state, notice previousState(), nextState() and printStatus() methods where we delegate the job to the state object. The states will be linked to each other and every state will set another one based on this reference passed to both methods.

正如我们所看到的,它包含了一个用于管理状态的引用,注意previousState(), nextState()和printStatus()方法,我们将工作委托给状态对象。这些状态将相互链接,每个状态将根据传递给这两个方法的this引用设置另一个状态。

The client will interact with the Package class, yet he won’t have to deal with setting the states, all the client has to do is go to the next or previous state.

客户端将与Package类进行交互,然而他不必处理设置状态的问题,客户端所要做的就是转到下一个或上一个状态。

Next, we’re going to have the PackageState which has three methods with the following signatures:

接下来,我们将有PackageState,它有三个方法,签名如下。

public interface PackageState {

    void next(Package pkg);
    void prev(Package pkg);
    void printStatus();
}

This interface will be implemented by each concrete state class.

这个接口将由每个具体的状态类来实现。

The first concrete state will be OrderedState:

第一个具体状态将是OrderedState

public class OrderedState implements PackageState {

    @Override
    public void next(Package pkg) {
        pkg.setState(new DeliveredState());
    }

    @Override
    public void prev(Package pkg) {
        System.out.println("The package is in its root state.");
    }

    @Override
    public void printStatus() {
        System.out.println("Package ordered, not delivered to the office yet.");
    }
}

Here, we point to the next state which will occur after the package is ordered. The ordered state is our root state and we mark it explicitly. We can see in both methods how the transition between states is handled.

在这里,我们指向下一个状态,它将在包被订购后发生。被订购的状态是我们的根状态,我们明确地标记它。我们可以看到这两种方法都是如何处理状态间的转换的。

Let’s have a look at the DeliveredState class:

让我们看一下DeliveredState类。

public class DeliveredState implements PackageState {

    @Override
    public void next(Package pkg) {
        pkg.setState(new ReceivedState());
    }

    @Override
    public void prev(Package pkg) {
        pkg.setState(new OrderedState());
    }

    @Override
    public void printStatus() {
        System.out.println("Package delivered to post office, not received yet.");
    }
}

Again, we see the linking between the states. The package is changing its state from ordered to delivered, the message in the printStatus() changes as well.

再一次,我们看到了状态之间的联系。包的状态从订购变为交付,printStatus()中的信息也随之改变。

The last status is ReceivedState:

最后一个状态是ReceivedState

public class ReceivedState implements PackageState {

    @Override
    public void next(Package pkg) {
        System.out.println("This package is already received by a client.");
    }

    @Override
    public void prev(Package pkg) {
        pkg.setState(new DeliveredState());
    }
}

This is where we reach the last state, we can only rollback to the previous state.

这就是我们到达最后一个状态的地方,我们只能回滚到之前的状态。

We already see there is some payoff since one state knows about the other. We’re making them tightly coupled.

我们已经看到有一些回报,因为一个国家知道另一个国家的情况。我们正在使它们紧密地结合起来。

5. Testing

5.测试

Let’s see how the implementation behaves. First, let’s verify whether setup transitions work as expected:

让我们看看实现的情况如何。首先,让我们验证一下设置转换是否按预期进行。

@Test
public void givenNewPackage_whenPackageReceived_thenStateReceived() {
    Package pkg = new Package();

    assertThat(pkg.getState(), instanceOf(OrderedState.class));
    pkg.nextState();

    assertThat(pkg.getState(), instanceOf(DeliveredState.class));
    pkg.nextState();

    assertThat(pkg.getState(), instanceOf(ReceivedState.class));
}

Then, quick check if our package can move back with its state:

然后,快速检查我们的包是否能以其状态移动回来。

@Test
public void givenDeliveredPackage_whenPrevState_thenStateOrdered() {
    Package pkg = new Package();
    pkg.setState(new DeliveredState());
    pkg.previousState();

    assertThat(pkg.getState(), instanceOf(OrderedState.class));
}

After that, let’s verify change the state and see how the implementation of printStatus() method changes its implementation at runtime:

之后,让我们验证一下改变状态,看看printStatus()方法的实现如何在运行时改变其实现。

public class StateDemo {

    public static void main(String[] args) {

        Package pkg = new Package();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();
    }
}

This will give us the following output:

这将给我们带来以下输出。

Package ordered, not delivered to the office yet.
Package delivered to post office, not received yet.
Package was received by client.
This package is already received by a client.
Package was received by client.

As we’ve been changing the state of our context, the behavior was changing but the class remains the same. As well as the API we make use of.

由于我们一直在改变我们的上下文的状态,行为在改变,但类仍然是相同的。以及我们利用的API。

Also, the transition between the states has occurred, our class changed its state and consequentially its behavior.

另外,状态之间的转换已经发生,我们的类改变了它的状态,并因此改变了它的行为。

6. Downsides

6.缺点

State pattern drawback is the payoff when implementing transition between the states. That makes the state hardcoded, which is a bad practice in general.

状态模式的缺点是在实现状态之间的转换时的回报。这使得状态被硬编码,这在一般情况下是一种不好的做法。

But, depending on our needs and requirements, that might or might not be an issue.

但是,根据我们的需要和要求,这可能是也可能不是一个问题。

7. State vs. Strategy Pattern

7.国家与战略模式

Both design patterns are very similar, but their UML diagram is the same, with the idea behind them slightly different.

这两种设计模式非常相似,但它们的UML图是一样的,背后的想法略有不同。

First, the strategy pattern defines a family of interchangeable algorithms. Generally, they achieve the same goal, but with a different implementation, for example, sorting or rendering algorithms.

首先,strategy 模式定义了一个可互换的算法系列。一般来说,它们实现了相同的目标,但实现方式不同,例如,排序或渲染算法。

In state pattern, the behavior might change completely, based on actual state.

在状态模式中,行为可能完全改变,基于实际状态。

Next, in strategy, the client has to be aware of the possible strategies to use and change them explicitly. Whereas in state pattern, each state is linked to another and create the flow as in Finite State Machine.

接下来,在策略中,客户必须意识到可能使用的策略并明确地改变它们。而在状态模式中,每个状态都与另一个状态相联系,并像有限状态机那样创建流程。

8. Conclusion

8.结论

The state design pattern is great when we want to avoid primitive if/else statements. Instead, we extract the logic to separate classes and let our context object delegate the behavior to the methods implemented in the state class. Besides, we can leverage the transitions between the states, where one state can alter the state of the context.

当我们想避免原始的if/else语句时,状态设计模式非常好。相反,我们将逻辑提取到独立的类中,让我们的上下文对象将行为委托给状态类中实现的方法。此外,我们可以利用状态之间的转换,其中一个状态可以改变上下文的状态。

In general, this design pattern is great for relatively simple applications, but for a more advanced approach, we can have a look at Spring’s State Machine tutorial.

一般来说,这种设计模式对于相对简单的应用程序来说是非常好的,但是如果想获得更高级的方法,我们可以看看Spring的状态机教程

As usual, the complete code is available on the GitHub project.

像往常一样,完整的代码可以在GitHub项目上获得。