状态模式
- Context环境类: 环境类中维护一个State对象,他是定义了当前的状态
- State抽象状态类:每一个类封装了一个状态对应的行为
3.结构图
- 已预定
- 已入住
- 已空闲
5.代码示例
//状态接口 public interface State { void handler(); } //空闲状态的处理 public class FreeStage implements State { @Override public void handler() { System.out.println("空闲状态"); } } //预定状态的处理 public class BookState implements State { @Override public void handler() { System.out.println("预定状态"); } } //入住状态的处理 public class CheckedInState implements State { @Override public void handler() { System.out.println("已入住状态"); } } //Context类状态组合类 public class Context { private State state;//组合 public void setState(State s){ System.out.println("修改状态!"); this.state = s; state.handler(); } } public class Client { public static void main(String[] args){ Context context = new Context(); context.setState(new FreeStage()); context.setState(new BookState()); context.setState(new CheckedInState()); } }
概述
我们平常在开发业务模块时,经常会遇到比较复杂的状态转换。比如说用户可能有新注册、实名认证中、已实名认证、禁用等状态,支付可能有等待支付、支付中、已支付等状态。OA 系统里的状态处理就更多了。遇到这些处理,很多人可能不假思索的就用最直观的 if/else 或者 switch 来判断状态的方式。但其实除了这种简单粗暴的方式,我们还有其他更好的方式来处理复杂的状态转换。
状态判断
我们就以支付为例,一笔订单可能有 等待支付
、支付中
、已支付
等状态。对于等待支付
的订单,用户可能通过第三方支付如微信支付或支付宝进行付款,支付完成后第三方支付会回调通知支付结果。我们可能会这样来处理:
public void pay(Order order) {
if (order.status == UNPAID) { order.status = PAYING; // 处理支付 } else throw IllegalStateException("不能支付");}
public void paySuccess(Order order) { if (order.status == PAYING) { // 处理支付成功通知 order.status = PAID; } else throw IllegalStateException("不能支付");
}
这样看起来好像没什么问题。但是假设我们允许用户多次支付完成一笔订单,于是我们需要增加一个 部分支付
状态。订单在 部分支付
状态时,可以进行下一步的支付;订单收到支付成功通知时,根据支付金额,可能会转换到 已支付
或 部分支付
状态。现在,我们不得不在 pay
和 paySuccess
里处理这个状态。
public void pay(Order order) {
if (order.status == UNPAID || order.status == PARTIAL_PAID) { order.status = PAYING; // 处理支付 } else throw IllegalStateException("不能支付");} public void paySuccess(Order order) { if (order.status == PAYING) { // 处理支付成功通知 if (order.paidFee == order.totalFee) order.status = PAID; else order.status = PARTIAL_PAID; } else throw IllegalStateException("不能支付");}
有了支付,我们必须也要能支持退款,那就需要增加 退款中
和 已退款
状态,以及对应的退款操作和退款成功回调处理。
public void refund(Order order) {
if (order.status == PAID || order.status == PARTIAL_PAID) {
order.status = REFUNDING;
// 处理退款 } else throw IllegalStateException("不能退款");
}
public void refundSuccess(Order order) { if (order.status == REFUNDING) {
// 处理退款成功通知
order.status = REFUNDED;
} else throw IllegalStateException("不能退款");
}
如果用一个有限状态机(FSM)来表示目前的状态转换,那大概是这样的:
对于状态不多、转换也不是很复杂的情况,用状态判断来处理还也算简洁明了。但一旦状态变多,操作变复杂,那么业务代码就会充斥各种条件判断,各种状态处理逻辑散落在各处。这时如果要新增一种状态,或者调整一些处理逻辑,就会比较麻烦,还很容易出错。
例如本例中,实际处理时可能还存在取消订单、支付失败/超时、退款失败/超时等情况,如果再加上物流以及一些内部状态,那处理起来就极其复杂了,而且一不小心还会出现支付失败了还能给用户退款,或者已经退款了还给用户发货等不应该出现的情况。这其实是一种坏味道,会造成代码不易维护和扩展。
设计模式之状态模式
不少人接下来可能会想到 GOF 的状态模式。对于涉及复杂状态逻辑的处理,使用状态模式可以将具体的状态抽象出来,而不是分散在各个方法的条件判断处理中,更容易维护和扩展。
状态模式一般包含三种角色,Context、State 和 ConcreteState。其中 State 是状态接口,定义了状态的操作;而 ConcreteState 则是各个具体状态的实现。它门的关系如下图所示:
下面我们尝试用状态模式实现前面的订单状态转换。首先我们需要定义状态接口,它应该包含所有需要的操作,以及每个状态对应的实现。
abstract class OrderState {
public abstract OrderState pay(Order order); public abstract OrderState paySuccess(Order order); public abstract OrderState refund(Order order); public abstract OrderState refundSuccess(Order order);
}
public class PayingOrderState implement OrderState { public OrderState pay(Order order) { throw IllegalStateException("已经在支付中"); }
public OrderState paySuccess(Order order, long fee) { doPaySuccess(Order order, long fee); if (order.paidFee < order.totalFee) { order.setState(new PartialPaidOrderState()); } else { order.setState(new PaidOrderState()); } } public OrderState refund(Order order) { throw IllegalStateException("尚未完成支付"); } public OrderState refundSuccess(Order order) { throw IllegalStateException("尚未完成支付"); }
}
public class UnpaidOrder implement OrderState { ... }
public class PartialPaidOrderState implement OrderState { ... }
public class PaidOrderState implement OrderState { ... }
public class RefundingOrderState implement OrderState { ... }
public class RefundedOrderState implement OrderState { ... }
大家可能会注意到,不是每个状态都支持所有操作的。例如上面的实现,PayingOrderState 是不能 refund 的,PaidOrderState 是不能 Pay 的,这里我们抛出了一个 IllegalStateException 异常。当然也可以不抛异常,而是放一个空的实现。或者我们也可以定义一个 Abstract Class,把操作的默认实现都放到里面,每个状态类只需要改写自己支持的方法。
然后我们要实现 Context,也就是我们的 Order 实体,它包含了一个状态字段 state,通过 state 实现所有的状态转换逻辑。定义好了这些,支付服务的实现就很简单了。
public class Order {
OrderState state = new UnpaidOrder(); public void pay(long fee) { state.pay(fee); } public void paySuccess(long fee) { state.paySuccess(this, fee); } public void refund() { ... } public void refundSuccess() { ... }
}
public class PaymentService { public void payOrder(long orderId) { Order order = OrderRepository.find(orderId) order.pay(); OrderRepository.save(order); }
}
通过状态模式,我们避免了代码里出现大量状态判断,状态转换规则的实现更加清晰。不过需要注意的是,实际上状态模式并不是很符合开闭原则(Open/Close Principle),新增一个状态时,还是可能要修改已有的其他状态的逻辑。但是和状态判断的方法比起来,已经清晰并且方便很多了。
领域驱动设计之状态建模
前面也提到了,状态模式的另外一个问题就是,实际业务里面有很多操作其实只对部分状态有效,而状态模式要求每个状态都要实现所有操作,有时候这是没有必要的。
对于这种情况,在领域驱动设计里,会更建议大家使用显式状态建模的方式。也就是把不同状态的实体,建模成不同的实体类;或者每个实体类代表一组状态。
例如,我们可以对每种状态的订单,都定义一个实体类。不过因为可能有多种状态的订单支持同样的操作,为了抽象这类操作,我们需要先定义一些接口。
public interface CanPayOrder {
Order pay();
}
public interface CanPaySuccessOrder { ... }
public interface CanRefundOrder { ... }
public interface CanRefundSuccessOrder { ... }
public class UnpaidOrder implements CanPayOrder { ... }
public class PayingOrder implements CanPaySuccessOrder { ... }
public class PartialPaidOrder implements CanPayOrder, CanRefundOrder { ... }
public class PaidOrder implements CanRefundOrder { ... }
public class RefundingOrder implements CanRefundSuccessOrder { ... }
public class PaymentService { public void pay(long orderId) { Order order = OrderRepository.find(orderId) // 转换为 CanPayOrder,如果无法转换则抛异常 CanPayOrder orderToPay = order.asCanPayOrder(); Order payingOrder = orderToPay.pay(); OrderRepository.save(payingOrder); }
}
每种状态的实体能支持的操作,都是显式定义好的。这种方式对于操作比较多,并且很多操作只对部分状态有效的情况,能够有效避免状态模式的缺点,代码更简洁清晰。
动态语言里的状态转换
上面的例子里,UnpaidOrder 和 PartialPaidOrder 都可以进行 pay 操作。其实处理支付操作的时候,我们不需要知道它是 UnpaidOrder 还是 PartialPaidOrder,我只需要知道当前订单实体支持 Pay 操作就可以了。在 Java 这样的静态类型语言里,我们只能通过定义一些 Interface 或者 Abstract class 来处理,还是有一点点麻烦。
如果是动态类型语言,例如 Python、Ruby 或者 JavaScript 等,还可以通过 Duck Typing 来进一步简化。所谓 Duck Typing 就是:“如果有一只鸟,它走起来像鸭子,游起来像鸭子,叫起来也像鸭子,我们就叫它鸭子。” 意思就是说,我们可以忽略对象的类型,直接在运行时判断对象是否支持某种行为。
例如在 JavaScript 里,我们获取到 order 实体后,就可以通过判断是否定义了 pay 方法,然后直接调用即可,而不必了解对象到底是什么类型。
let orderToPay = order.asOrderStateEntity();
if (typeof orderToPay['pay'] === 'function') { orderToPay.pay();
} else { throw new ServiceError("该订单不能进行支付操作");
}
当然,实际会用动态语言开发这种业务系统的并不多,毕竟动态语言也会引起其他方面的一些问题。
FSM
不管是状态模式还是状态实体,多个状态之间的转换,还是分散在各个状态的实现里的。其实所有的状态转换都可以概括为:F(S, E) -> (A, S')
,即如果当前状态为S,接收到一个事件E,则执行动作A,同时状态转换为S‘。
Akka 里面提供了一个有限状态机的框架叫 FSM,通过 Scala 语言的模式匹配及其他一些强大特性,可以把状态转换和业务处理逻辑分离开来。具体我就不细说了,我们也没有在实际开发中使用过。但我们可以感受一下:
class OrderFSM extends FSM[State, Data] {
startWith(Unpaid) // 开始时的状态是 Unpaid when(Unpaid) { case Event(Pay, data) ⇒ // Unpaid 状态时,如果收到事件 Pay,则进行支付,状态转换为 Paying doPay(data) goto(Paying) } when(Paying) { // Paying 状态时,如果收到事件 PaySuccess,则进行支付成功处理,通过根据支付金额,转换为 Paid 或者 PartialPaid 状态 case Event(PaySuccess(fee), data) ⇒ doPaySuccess(data, fee) if (fee+data.paidFee == data.totalFee) goto(Paid) else goto(PartialPaid) } // ...
}
当然,FSM 的功能远不止此,实际实现也可能会更复杂。Java 里面也有一个有限状态机的实现,叫 Squirrel,不过由于 Java 语言的限制,使用起来没有 Akka FSM 那么优雅。这里就不深入研究了,感兴趣的同学可以去了解下。
总结
本文简单介绍了业务系统中,处理复杂状态逻辑的几种方法。除了极其简单的情况,大家应该尽量避免使用状态判断的方式,使用状态模式或者状态建模,可以很有效的提高代码的维护性和扩展性。最后也简单介绍了动态语言对状态建模的一些优化,以及 FSM 框架。
相关推荐
状态模式是一种行为设计模式,它允许对象在内部状态改变时改变其行为,对象看起来似乎修改了它的类。这种模式常用于处理对象的状态变化,使得代码更易于理解和维护。在这个实例中,我们将通过Java来深入理解并应用...
状态模式是一种行为设计模式,它允许对象在内部状态改变时改变其行为,看起来好像对象改变了它的类。在Java中,状态模式通常通过定义不同的状态类和一个上下文类来实现,其中上下文类持有状态对象并调用其方法来响应...
状态模式在Android编程中是一种非常重要的设计模式,它主要用于处理对象在不同状态下的不同行为。状态模式的核心思想是将每一种状态封装成一个类,使得对象可以在运行时根据其内部状态的变化来改变其行为。 一、...
状态模式是一种行为设计模式,它使对象能够在内部状态改变时改变其行为,看起来好像改变了它的类。这种模式常用于处理对象在不同状态下表现各异的情况,避免了复杂的条件语句,提高了代码的可读性和可维护性。 在...
状态模式是一种行为设计模式,它允许对象在内部状态改变时改变其行为,对象看起来似乎修改了它的类。这种模式常用于处理对象的状态变化,使得代码更易于理解和维护。在Java中,状态模式通常通过实现接口或者继承抽象...
状态模式是一种行为设计模式,它允许对象在内部状态改变时改变其行为,对象看起来似乎修改了它的类。这种模式常用于处理对象的状态变化,并且根据不同的状态,对象的行为也相应地变化。 在Java中实现状态模式,我们...
状态模式是一种行为设计模式,它允许对象在内部状态改变时改变其行为,看起来好像对象改变了它的类。在C#中,状态模式常用于处理对象在不同状态下的多种行为,使得代码更加灵活,易于扩展和维护。 状态模式的核心...
状态模式是一种行为设计模式,它使你能在运行时改变对象的行为。在状态模式中,一个对象的状态变化可以导致其行为的变化。这种模式常用于当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变其行为的...
首先了解什么是状态模式,然后思考为何产生这种模式,意义何在。同时能够在实际的代码中体现这种模式,并且对比使用该模式与不使用的区别何在。
### 设计模式之状态模式详解 #### 一、设计模式概述 设计模式是软件工程领域的一种最佳实践,它提供了一套解决特定问题的通用方案。根据功能的不同,设计模式大致可以分为三大类:创建型模式、结构型模式以及行为型...
根据提供的信息,我们可以深入探讨《Head First 设计模式》中的状态模式这一章节。状态模式是设计模式中的一种,主要用于处理对象状态变化时的行为差异。接下来,我们将详细解析状态模式的概念、应用场景、结构组成...
状态模式是一种行为设计模式,它使你能在运行时改变对象的行为。在状态模式中,一个对象的状态变化会导致其行为的变化,这种变化不是通过改变对象的类来实现的,而是通过改变对象的状态。这个模式的核心是封装可能...
### 设计模式之状态模式详解 #### 一、概述 状态模式(State Pattern)是一种行为设计模式,它允许对象在其内部状态改变时改变其行为,从而使对象看起来像改变了其类。这种模式的主要目的是将对象的状态作为独立的...
本文实例讲述了Python设计模式之状态模式原理与用法。分享给大家供大家参考,具体如下: 状态模式(State Pattern):当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类 下面是一个状态模式的...
Java 设计模式之策略模式与状态模式 策略模式是 Java 中的一种设计模式,它主要用于解决系统与第三方接口进行数据交互的问题。当系统需要与多种格式的数据进行交互时,使用策略模式可以很好地解决这个问题。例如,...
状态模式是一种行为设计模式,它允许对象在内部状态改变时改变其行为,对象看起来似乎修改了它的类。这种模式常用于处理对象的状态变化,并且在不同的状态下,对象的行为也相应地变化。在Java中,我们可以利用接口、...
本文实例讲述了PHP设计模式之状态模式定义与用法。分享给大家供大家参考,具体如下: 什么是状态设计模式 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。 状态模式主要解决的是当控制一...