1.简介
在软件开发过程中,应用程序中的部分对象可能会根据不同的情况作出不同的行为,我们把这些对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。
对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用if/else或switch-case语句来做状态判断,在进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的if/else语句,这违背了“开闭原则”,不利于程序的扩展。
以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除if/else、switch/case等冗余语句,代码更有层次性,并且具备良好的扩展性。
2.定义
状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
3.优点
- 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
4.缺点
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
- 状态模式对开闭原则的支持不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态装换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。
5.结构
状态模式主要包含以下角色:
- 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
- 抽象状态(State)角色:定义一个接口,用于封装环境对象中特定状态所对应的行为,可以有一个或多个行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
结构图如下:
6.应用场景
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
7.代码样例
1.简单样例
/**
* 抽象状态类
*/
interface State{
void handle(Context context);
}
/**
* 具体状态类A
*/
class ConcreteStateA implements State{
@Override
public void handle(Context context) {
System.out.println("当前状态是A!");
context.setState(new ConcreteStateB());
}
}
/**
* 具体状态类B
*/
class ConcreteStateB implements State{
@Override
public void handle(Context context) {
System.out.println("当前状态是B!");
context.setState(new ConcreteStateA());
}
}
/**
* 环境类
*/
class Context{
@Getter
@Setter
private State state;
public Context(){
this.state = new ConcreteStateA();
}
public void handle(){
state.handle(this);
}
}
public class StatePatternSimpleTest {
public static void main(String[] args){
Context context = new Context();
context.handle();
context.handle();
context.handle();
context.handle();
}
}
2.状态模式的应用实例一
例:用状态模式设计一个学生成绩的状态转换程序。
分析:本实例包含了“不及格”、“中等”和“优秀”3种状态,当学生的分数小于60时为“不及格”状态,当分数大于等于60分且小于90分时为“中等”状态,当分数大于等于90分时为“优秀”状态,我们用状态模式来实现这个程序。
首先,定义一个抽象状态类(AbstractState),其中包含了环境属性、状态属性和当前分数属性,以及加减分方法addScore(int x)和检查当前状态的抽象方法checkState()。
然后,定义“不及格”状态类LowState、“中等”状态类MiddleState和“优秀”状态类HighState,他们是具体状态类,实现checkState()方法,负责检查自己的状态,并根据情况转换。
最后,定义环境类(ScoreContext),其中包含了当前状态对象和加减分的方法add(int score),客户类通过该方法来改变成绩状态。
结构图如下:
代码样例:
class ScoreContext{
@Setter
@Getter
private AbstractState state;
public ScoreContext(){
state = new LowState(this);
}
public void add(int score){
state.addScore(score);
}
}
abstract class AbstractState{
protected ScoreContext context;
protected String stateName;
protected int score;
public abstract void checkState();
public void addScore(int x){
score += x;
System.out.print("加上:" + x + "分,当前分数为:" + score);
checkState();
System.out.println("当前状态为:" + context.getState().stateName);
}
}
class LowState extends AbstractState{
public LowState(ScoreContext context){
this.context = context;
stateName = "不及格";
score = 0;
}
public LowState(AbstractState state){
this.context = state.context;
stateName = "不及格";
score = state.score;
}
@Override
public void checkState() {
if(score >= 90){
context.setState(new HighState(this));
}else if(score >= 60){
context.setState(new MiddleState(this));
}
}
}
class MiddleState extends AbstractState{
public MiddleState(AbstractState state){
context = state.context;
stateName = "中等";
score = state.score;
}
@Override
public void checkState() {
if(score < 60){
context.setState(new LowState(this));
}else if(score >= 90){
context.setState(new HighState(this));
}
}
}
class HighState extends AbstractState{
public HighState(AbstractState state){
context = state.context;
stateName = "优秀";
score = state.score;
}
@Override
public void checkState() {
if(score < 60){
context.setState(new LowState(this));
}else if(score < 90){
context.setState(new MiddleState(this));
}
}
}
public class StatePatternScoreTest {
public static void main(String[] args){
ScoreContext account = new ScoreContext();
System.out.println("学生成绩状态测试:");
account.add(30);
account.add(40);
account.add(25);
account.add(-15);
account.add(-25);
}
}
3.状态模式的应用实例二
例:用“状态模式”设计一个多线程的状态转换程序。
分析:多线程存在5种状态,分别为新建状态、就绪状态、运行状态、阻塞状态和死亡状态,各个状态当遇到相关方法调用或事件触发时会转换到其他状态,其状态转换规律如下:
先定义一个抽象状态类(ThreadState),然后为图3所示的每个状态设计一个具体状态类,他们是新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead),每个状态中有触发他们转变的方法,环境类(ThreadContext)中先生成一个初始状态(New),并提供相关触发方法。
结构图如下:
代码样例:
/**
* 环境类
*/
class ThreadContext{
@Getter
@Setter
private ThreadState state;
public ThreadContext(){
state = new New();
}
public void start(){
((New)state).start(this);
}
public void getCPU(){
((Runnable)state).getCPU(this);
}
public void suspend(){
((Running)state).suspend(this);
}
public void stop(){
((Running)state).stop(this);
}
public void resume(){
((Blocked)state).resume(this);
}
}
/**
* 抽象状态类:线程状态
*/
abstract class ThreadState{
protected String stateName;
}
/**
* 具体状态类:新建状态
*/
class New extends ThreadState{
public New(){
stateName = "新建状态";
System.out.println("当前线程处于:新建状态!");
}
public void start(ThreadContext context){
System.out.print("调用start()方法->");
if(stateName.equals("新建状态")){
context.setState(new Runnable());
}else{
System.out.println("当前线程不是新建状态, 不能调用start()方法!");
}
}
}
/**
* 具体状态类:就绪状态
*/
class Runnable extends ThreadState{
public Runnable(){
stateName = "就绪状态";
System.out.println("当前线程处于:就绪状态!");
}
public void getCPU(ThreadContext context){
System.out.print("获得CPU时间->");
if(stateName.equals("就绪状态")){
context.setState(new Running());
}else{
System.out.println("当前线程不是就绪状态,不能获取CPU");
}
}
}
/**
* 具体状态类:运行状态
*/
class Running extends ThreadState{
public Running(){
stateName = "运行状态";
System.out.println("当前线程能处于:运行状态!");
}
public void suspend(ThreadContext context){
System.out.print("调用suspend()方法->");
if(stateName.equals("运行状态")){
context.setState(new Blocked());
}else{
System.out.println("当前线程不是运行状态,不能调用stop()方法!");
}
}
public void stop(ThreadContext context){
System.out.print("调用stop()方法->");
if(stateName.equals("运行状态")){
context.setState(new Dead());
}else{
System.out.println("当前线程不是运行状态,不能调用stop()方法!");
}
}
}
/**
* 具体状态类:阻塞状态
*/
class Blocked extends ThreadState{
public Blocked(){
stateName = "阻塞状态";
System.out.println("当前线程处于:阻塞状态!");
}
public void resume(ThreadContext context){
System.out.print("调用resume()方法->");
if(stateName.equals("阻塞状态")){
context.setState(new Runnable());
}
}
}
/**
* 具体状态类:死亡状态
*/
class Dead extends ThreadState{
public Dead(){
stateName = "死亡状态";
System.out.println("当前线程处于:死亡状态!");
}
}
public class StatePatternThreadTest {
public static void main(String[] args){
ThreadContext context = new ThreadContext();
context.start();
context.getCPU();
context.suspend();
context.resume();
context.getCPU();
context.stop();
}
}
4.状态模式的扩展
在某些情况下,可能会有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享。
结构如如下:
代码样例
class ShareContext{
@Setter
private ShareState state;
private HashMap<String, ShareState> stateHashMap = new HashMap<>();
public ShareContext(){
state = new ConcreteStateA();
stateHashMap.put("1", state);
state = new ConcreteStateB();
stateHashMap.put("2", state);
state = getState("1");
}
public ShareState getState(String key){
ShareState state= stateHashMap.get(key);
return state;
}
public void handle(){
state.handle(this);
}
}
abstract class ShareState{
public abstract void handle(ShareContext context);
}
class ConcreteStateA extends ShareState{
@Override
public void handle(ShareContext context) {
System.out.println("当前状态是:状态1");
context.setState(context.getState("2"));
}
}
class ConcreteStateB extends ShareState{
@Override
public void handle(ShareContext context) {
System.out.println("当前状态是:状态2");
context.setState(context.getState("1"));
}
}
public class StatePatternFlyweightTest {
public static void main(String[] args){
ShareContext context = new ShareContext();
context.handle();
context.handle();
context.handle();
context.handle();
}
}
5.使用状态模式自由切换登录状态
日常开发过程中,登录和未登录两种状态分别有不同的操作,比如登录后才能评论、收藏、支付购买和编辑资料等操作。我们网购时看到心仪的商品,如果处于登录状态,可以加购物车或直接购买,否则会跳转到登录页面,登录后再继续执行先前的动作。这里涉及的状态有两种,即登录和未登录。行为有两种,即加购物车和直接购买。
如果使用if/else语句实现,代码如下:
public void addShopping(String userState){
if("未登录".equals(userState)){
System.out.println("去登录");
return;
}
System.out.println("加入购物车!");
}
public void buy(String userState){
if("未登录".equals(userState)){
System.out.println("去登录");
return;
}
System.out.println("直接购买!");
}
下面使用状态模式实现这个逻辑。
class AppContext{
public static final UserState STATE_LOGIN = new LoginState();
public static final UserState STATE_UN_LOGIN = new UnLoginState();
@Getter
@Setter
private UserState currentState = STATE_UN_LOGIN;
{
STATE_LOGIN.setContext(this);
STATE_UN_LOGIN.setContext(this);
}
public void addShopping(){
this.currentState.addShopping();
}
public void buy(String buy){
this.currentState.buy(buy);
}
}
abstract class UserState{
@Getter
@Setter
protected AppContext context;
public abstract void addShopping();
public abstract void buy(String buy);
}
class LoginState extends UserState{
@Override
public void addShopping() {
System.out.println("加入购物车成功");
}
@Override
public void buy(String buy) {
System.out.println(buy);
}
}
class UnLoginState extends UserState{
@Override
public void addShopping() {
this.jumpLogin();
this.context.getCurrentState().addShopping();
}
@Override
public void buy(String buy) {
}
private void jumpLogin(){
System.out.println("跳转到登录页面!");
this.context.setCurrentState(AppContext.STATE_LOGIN);
}
}
public class StatePatternLoginTest {
public static void main(String[] args){
AppContext context = new AppContext();
context.addShopping();
context.buy("直接购买");
}
}
6.使用状态机(Spring Statemachine)实现订单状态流转控制
在电商平台中,一个订单会有多种状态,临时单、已下单、待支付、已支付、待发货、待收货、已完成等等。每一种状态都和变化前的状态以及执行的操作有关。
比如,用户将商品加入购物车后,后台会生成一个所谓的“临时单”。因为用户还没有点击下单,所以这个订单实际上还没有生成。只有当用户下单后,这个“临时单”才会转化为一个“待支付的订单”。
以上过程中只有将一个处于“临时单”状态的订单执行下单操作,才能得到一个状态为“待支付”的订单。即一个前置状态+一个恰当的操作,才能流转订单的状态。在这个过程中如果使用硬编码,我们就需要一系列的if/else语句来检查订单的当前状态、可执行操作以及这个两个组合得到的下一个应该被流转的状态值。如果订单的状态流转很复杂,代码逻辑就会很复杂,可读性低,后期维护困难。
处理以上问题,我们可以使用状态设计模式来处理。对应到实践,就是状态机。
状态机是状态模式的一种应用,相当于上下文角色的一个升级版。在工作流或游戏等各种系统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。状态机可以帮助开发者简化状态控制的开发过程,让状态机结构更加层次话。
Spring提供了一个很好的解决方案,Spring Statemachine(状态机)是应用程序开发人员在Spring应用程序中使用状态机概念的框架。
下面是用Spring状态机模拟订单状态流转的过程。
1.添加依赖
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
2.创建订单状态枚举类和状态转换枚举类
public enum OrderStatus {
/**
* 待支付
*/
WAIT_PAYMENT,
/**
* 待发货
*/
WAIT_DELIVER,
/**
* 待收货
*/
WAIT_RECEIVE,
/**
* 已完成
*/
FINISH;
}
public enum OrderStatusChangeEvent {
/**
* 支付
*/
PAYED,
/**
* 发货
*/
DELIVERY,
/**
* 确认收货
*/
RECEIVED;
}
3.创建订单实体类
@Getter
@Setter
@ToString
public class Order {
private int id;
private OrderStatus status;
}
4.添加状态流配置
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig
extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
/**
* 配置状态
* @param states
* @throws Exception
*/
@Override
public void configure(StateMachineStateConfigurer<OrderStatus,
OrderStatusChangeEvent> states)throws Exception{
states.withStates()
.initial(OrderStatus.WAIT_PAYMENT)
.states(EnumSet.allOf(OrderStatus.class));
}
/**
* 配置状态转换事件关系
* @param transitions
* @throws Exception
*/
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus,
OrderStatusChangeEvent> transitions) throws Exception{
transitions.withExternal()
.source(OrderStatus.WAIT_PAYMENT)
.target(OrderStatus.WAIT_DELIVER)
.event(OrderStatusChangeEvent.PAYED)
.and()
.withExternal()
.source(OrderStatus.WAIT_DELIVER)
.target(OrderStatus.WAIT_RECEIVE)
.event(OrderStatusChangeEvent.DELIVERY)
.and()
.withExternal()
.source(OrderStatus.WAIT_RECEIVE)
.target(OrderStatus.FINISH)
.event(OrderStatusChangeEvent.RECEIVED);
}
/**
* 持久化配置
* 实际使用中,可以配合redis等,进行持久化配置
* @return
*/
@Bean
public DefaultStateMachinePersister persister(){
return new DefaultStateMachinePersister(new StateMachinePersist<Object, Object, Order>() {
@Override
public void write(StateMachineContext<Object, Object> stateMachineContext, Order order) throws Exception {
//此处进行持久化操作
System.out.println("进行持久化操作!");
}
@Override
public StateMachineContext<Object, Object> read(Order order) throws Exception {
//此处直接获取Order中的状态,没有进行持有化读取操作
return new DefaultStateMachineContext<>(order.getStatus(),
null, null ,null, null);
}
});
}
}
5.添加订单状态监听器
@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl {
@OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
public boolean payTransition(Message<OrderStatusChangeEvent> message){
Order order = (Order) message.getHeaders().get("order");
order.setStatus(OrderStatus.WAIT_DELIVER);
System.out.println("支付,状态机反馈信息:" + message.getHeaders().toString());
return true;
}
@OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
public boolean deliverTransition(Message<OrderStatusChangeEvent> message){
Order order = (Order) message.getHeaders().get("order");
order.setStatus(OrderStatus.WAIT_RECEIVE);
System.out.println("发货,状态机反馈信息:" + message.getHeaders().toString());
return true;
}
@OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
public boolean receiveTransition(Message<OrderStatusChangeEvent> message){
Order order = (Order)message.getHeaders().get("order");
order.setStatus(OrderStatus.FINISH);
System.out.println("收货,状态机反馈信息:" + message.getHeaders().toString());
return true;
}
}
6.创建OrderService接口
public interface OrderService {
Order create();
Order pay(int id);
Order deliver(int id);
Order receive(int id);
Map<Integer, Order> getOrders();
}
7.Service业务逻辑
@Service("orderServiceImpl")
public class OrderServiceImpl implements OrderService {
@Autowired
private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
@Autowired
private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> persister;
private int id = 1;
private Map<Integer, Order> orders = new HashMap<>();
@Override
public Order create() {
Order order = new Order();
order.setStatus(OrderStatus.WAIT_PAYMENT);
order.setId(id++);
orders.put(order.getId(), order);
return order;
}
@Override
public Order pay(int id) {
Order order = orders.get(id);
System.out.println("线程名字:"
+ Thread.currentThread().getName()
+ "尝试支付,订单号:"
+ id);
Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.PAYED)
.setHeader("order", order)
.build();
if(!sendEvent(message, order)){
System.out.println("线程名称:" + Thread.currentThread().getName()
+ "支付失败,状态异常,订单号:"
+ id);
}
return orders.get(id);
}
@Override
public Order deliver(int id) {
Order order = orders.get(id);
System.out.println("线程名字:"
+ Thread.currentThread().getName()
+ "尝试发货,订单号:"
+ id);
Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.DELIVERY)
.setHeader("order", order)
.build();
if(!sendEvent(message, order)){
System.out.println("线程名称:" + Thread.currentThread().getName()
+ "发货失败,状态异常,订单号:"
+ id);
}
return orders.get(id);
}
@Override
public Order receive(int id) {
Order order = orders.get(id);
System.out.println("线程名字:"
+ Thread.currentThread().getName()
+ "尝试收货,订单号:"
+ id);
Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.RECEIVED)
.setHeader("order", order)
.build();
if(!sendEvent(message, order)){
System.out.println("线程名称:" + Thread.currentThread().getName()
+ "收货失败,状态异常,订单号:"
+ id);
}
return orders.get(id);
}
@Override
public Map<Integer, Order> getOrders() {
return orders;
}
/**
* 发送订单状态转换事件
* @param message
* @param order
* @return
*/
private synchronized boolean sendEvent(Message<OrderStatusChangeEvent> message
, Order order){
boolean result = false;
try{
orderStateMachine.start();
//尝试恢复状态机状态
persister.restore(orderStateMachine, order);
//添加延迟用于线程安全测试
Thread.sleep(1000);
result = orderStateMachine.sendEvent(message);
//持久化状态机状态
persister.persist(orderStateMachine, order);
}catch (Exception e){
e.printStackTrace();
}finally {
orderStateMachine.stop();
}
return result;
}
}
8.客户端测试代码
@SpringBootApplication
public class Test {
public static void main(String[] args){
Thread.currentThread().setName("主线程");
ConfigurableApplicationContext context = SpringApplication.run(Test.class, args);
OrderService orderService = (OrderService) context.getBean("orderServiceImpl");
orderService.create();
orderService.create();
orderService.pay(1);
new Thread("客户线程"){
public void run(){
orderService.deliver(1);
orderService.deliver(1);
}
}.start();
orderService.pay(2);
orderService.deliver(2);
orderService.deliver(2);
orderService.receive(2);
System.out.println("全部订单状态:" + orderService.getOrders());
}
}
7.状态模式在JSF源码中的应用
由于源码中一般只提供一种通用的解决方法,所以状态模式的具体应用在源码中非常少见。
JSF是一个比较经典的前端框架,在JSF中,所有页面的处理分为7个阶段,被定义在PhaseId类中,分别用不同的常量来表示周期阶段,源码如下:
private class PhaseId implements Comparable {
...
private static final PhaseId[] values = {
ANY_PHASE, // 任意一个生命周期阶段
RESTORE_VIEW, // 恢复视图阶段
APPLY_REQUEST_VALUES, // 应用请求值阶段
PROCESS_VALIDATIONS, // 处理输入校验阶段
UPDATE_MODEL_VALUES, // 更新模型的值阶段
INVOKE_APPLICATION, // 调用应用阶段
RENDER_RESPONSE // 显示响应阶段
};
...
}
这些状态的切换都在Lifecycle类的execute()方法中进行,其中会传入一个参数FacesContext对象,最终所有状态都被FacesContext保存。
package javax.faces.lifecycle;
import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseListener;
public abstract class Lifecycle {
public Lifecycle() {
}
public abstract void addPhaseListener(PhaseListener var1);
public abstract void execute(FacesContext var1) throws FacesException;
public abstract PhaseListener[] getPhaseListeners();
public abstract void removePhaseListener(PhaseListener var1);
public abstract void render(FacesContext var1) throws FacesException;
}
8.状态模式和责任链模式的区别
状态模式和责任链模式都能消除if/else分支过多的问题。但是在某些情况下,状态模式中的状态可以理解为责任,那么在这种情况下,两种模式都可以使用。
从定义来看,状态模式强调的是一个对象内在状态的改变,而责任链模式强调的是外部节点对象间的改变。
从代码实现上来看,两者最大的区别就是状态模式的各个状态对象知道自己要进入的下一个状态对象,而责任链模式并不清楚其下一个节点处理对象,因为链式组装是由客户端负责。
9.状态模式和策略模式的区别
策略模式类图:
状态模式类图:
状态模式和策略模式的UML类图架几乎完全一样,但两者的应用场景是不一样的。策略模式的多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法,而状态模式的各个状态间存在相互关系,彼此之间在一定条件下存在自动切换状态的效果,并且用户无法指定状态,只能设置初始状态。