Java中的状态设计模式
原文链接:www.baeldung.com/java-state-…
作者:baeldung
译者:jackzhaiyh
1、概述
本教程,介绍一种行为型GOF设计模式:状态模式。
首先,我们介绍一下此模式的目的和阐述其试图解决的问题。然后看一下状态模式的UML类图和一个实例。
2、状态设计模式
状态模式的主要思想是:允许object在不改变class的情况下改变自身的行为。同时,通过实现状态模式,代码可以消除过多的if/else语句,能够保持代码整洁。
想象一下我们有一个Package送去邮局,Package可以自己排队,然后递送邮局并最终被客户签收。现在,基于真实状态,我们想要打印Package的递送状态。
最简单的方法就是在class的各个方法中用一些boolean标识和简单的if/else语句实现。这样不会使简单场景复杂化。然而,如果我们需要处理的状态很多,这种方式会使逻辑、代码很混乱,不易管理。
除此之外,每种状态的逻辑会贯穿整个方法。这种时候就可以考虑使用状态模式。借助状态模式,我们可以将逻辑封装到独立的class中,符合单一责任原则和开放/封闭原则,并使代码简洁易于维护。
3、UML类图

UML类图中,Context class拥有一个可以在程序执行时改变的关联State。
Context代理State实现的行为。换句话说,所有进入Context的请求由具体的State执行。
以上设计使得逻辑是分离的,并且很容易增加新state,只需要增加一个State实现。
4、实现
我们设计一个应用。如上所属,Package可以排队、传递和接收,从而我们我们可以设计三种State和Context class。
首先,定义Context,这是个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();
}
}
Package类包含一个管理State的引用,注意:我们通过previousState(),nextState()和printStatus()方法为State对象分派任务。State可以互相转换,State对象的前两个方法可以基于传入的Context设置State的转换。
Client与Package交互,我们不必处理State的转换,所有Client只需要执行下一个或者前一个State。
下面,我们抽象一个PackageState interface出来,用于定义三个行为。
public interface PackageState {
void next(Package pkg);
void prev(Package pkg);
void printStatus();
}
所有具体State class需要实现这个interface。
第一个具体State是OrderState:
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.");
}
}
从以上代码中,我们指定Package排序之后的下一个State。我们明确标明排序State是起始State,从这两个方法能够看到State如何转移。
下面是DeliveredState class:
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.");
}
}
同样能够看到State的联系。Package从排序State转移到投递State,printStatus()方法的消息也随之改变。
最后一个State是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());
}
}
这是最后一个State,我们只能回滚到上一个State。
由于一个State需要了解另一个State的情况,我们付出了一定的紧密耦合的代价。
5、测试
首先,来看一下对象的行为方式,是否如预期一般进行转换。
@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));
}
然后,快速检查Package是否能够回退State。
@Test
public void givenDeliveredPackage_whenPrevState_thenStateOrdered() {
Package pkg = new Package();
pkg.setState(new DeliveredState());
pkg.previousState();
assertThat(pkg.getState(), instanceOf(OrderedState.class));
}
最后,验证在运行时Package改变State后,其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();
}
}
最终输出:
- 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.
当我们改变Context的State,Context的行为发生改变但是class并没有改变。
同时,当State转换后,class改变了他的State,从而改变了他的行为。
6、缺点
状态模式的缺点是在实现状态之间的转换时的代价。这使得State硬编码,这在一般情况下是不好的做法。
但是,根据我们的需求和要求,这可能是也可能不是问题。
7、状态模式vs策略模式
两者很相似,但相同的类图后面隐藏着细微的思想差别。
首先,策略模式定义了一组可互换的算法。通常这些算法可达到同样的目的,但是实现有区别,比如排序和渲染算法。
状态模式,基于真实状态,行为可以完全改变。
并且,策略模式中,调用者必须了解不同的策略并且明确的去改变使用。反之,状态模式,状态互相连接,就像有限状态机一样转换。
8、总结
当我们想要避免原始的if/else语句时,状态设计模式很好用。我们提取逻辑来分离class,让我们的_Context对象代理_state class中实现的方法。此外,我们可以利用State之间的转换,其中一个State可以改变Context的State。
一般来说,这种设计模式对于相对简单的应用程序来说非常有用,但是对于更高级的程序,我们可以看一下Spring的State Machine教程。
像往常一样,完整的代码可以在GitHub项目中找到。