定义
当一个对象的内部状态改变时允许改变其行为,这个对象看起来像是改变了其类。
由定义里可以看出,状态的改变是为了改变对象的行为,所以我们的思路就是将行为封装到对象中,然后利用多态机制来控制行为。
角色
状态模式有三个角色:
Context 角色: 负责保存所有的状态,然后提供对外的使用接口
抽象状态角色:可以是接口,也可以是抽象类,定义了状态可以进行的操作,也就是定义中的行为
具体状态角色:具体实现每个状态的行为
模式说明
以现实中的订单状态为例,在还是新人的时候订单的状态转换是用一堆的if else 来判断和实现的,印象里面由于状态越加越多,在判断一个订单是否能退款、退货时,判断条件已经达到几层嵌套。 后来交接给同事了,不知道有没有对此进行重构。现在想来,要想解决这个问题,状态模式是一个比较好的方法。
我们演示程序中订单状态只有未创建,已创建,已付款和已退款三个状态,具有订单状态的是订单详情对象,因此订单详情对象就是我们的 Context 角色。
首先来定义状态接口, 它具有三个方法,这些方法指定了状态之间可以进行的转换。
public interface OrderState {
void create(OrderDetail detail) throws UnsupportedOperationException;
void pay(OrderDetail detail) throws UnsupportedOperationException;
void refund(OrderDetail detail) throws UnsupportedOperationException;
}
虽然 UnsupportedOperationException
是一个 RuntimeException
,我们依然将它放到了方法签名中,这是为了符合里氏替换原则,就我看过的介绍状态模式的文章而言,对不支持的操作直接在状态实现中抛出一个异常,这是不符合里氏替换原则的,因为接口并没有这样的约定。
状态模式有一个缺点是新增状态时需要在接口中增加一个方法,导致所有的具体状态类都需要修改,违反了对修改封闭的原则,对此我的思考是新增状态时修改是不可避免的,但是可以避免不必要的转换路径修改。解决方法是增加一个抽象类,它描述的是不可转换路径,直接抛出异常。
public class AbstractOrderState implements OrderState {
@Override
public void create(OrderDetail detail) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public void pay(OrderDetail detail) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public void refund(OrderDetail detail) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
这样保证每新增一个状态时默认是不可达的,因此只需要修改转换路径上的类就可以了。接下来先定义订单的具体状态类,然后用图示来说明这个设计方案。
订单的初始四个状态定义及状态间的转换:
public class PreCreateOrderState extends AbstractOrderState {
@Override
public void create(OrderDetail detail) throws UnsupportedOperationException {
detail.setState(OrderDetail.ORDER_CREATED_STATE);
System.out.println("create an order");
}
}
public class CreatedOrderState extends AbstractOrderState {
@Override
public void pay(OrderDetail detail) throws UnsupportedOperationException {
detail.setState(OrderDetail.ORDER_PAYED_STATE);
System.out.println("order payed");
}
}
public class PayedOrderState extends AbstractOrderState {
@Override
public void refund(OrderDetail detail) throws UnsupportedOperationException {
detail.setState(OrderDetail.ORDER_REFUNDED_STATE);
System.out.println("order refunded");
}
}
/**
* final state, 不支持任何操作
*/
public class RefundedOrderState extends AbstractOrderState {
}
状态转换如下图所示:
如果现在要新增一个送达状态,我们只需要在 OrderState
接口中增加 deliver
方法,在 AbstractOrderState
中将其标记为不可达(即与其他方法一样抛出异常),然后在 PayedOrderState
中新增状态转换路径即可(即覆盖 deliver
方法转换到送达状态), 其他的状态类不需要改动。
最后定义Context 角色,它里面有必须的数据,状态实例和对外提供的API。
public class OrderDetail {
public static final OrderState ORDER_PRE_CREATE_STATE = new PreCreateOrderState();
public static final OrderState ORDER_CREATED_STATE = new CreatedOrderState();
public static final OrderState ORDER_PAYED_STATE = new PayedOrderState();
public static final OrderState ORDER_REFUNDED_STATE = new RefundedOrderState();
private int id;
private String good;
private OrderState orderState;
public OrderDetail() {
this.orderState = ORDER_PRE_CREATE_STATE;
}
public OrderState getState() {
return orderState;
}
void setState(OrderState state) {
this.orderState = state;
}
public void create() throws UnsupportedOperationException {
orderState.create(this);
}
public void pay() throws UnsupportedOperationException {
orderState.pay(this);
}
public void refund() throws UnsupportedOperationException {
orderState.refund(this);
}
}
虽然这里提供的API 和 OrderState
里的一样,但是不建议实现 OrderState
接口,因为它不是一个 OrderState
(如果我们起名叫OrderOperation
,就可以实现了)。
这个 OrderDetail
对象和贫血数据库Entity 不一样,它是一个Domain Entity,封装了自己的操作。
优点
- 相对与杂糅在一起的判断条件,状态模式显式地定义了状态,并将状态对应的操作内聚在一起
- 状态的转换由具体状态类负责,
- 在不增加行为时,新增状态不影响其他状态类,符合开闭原则
缺点
- 增加行为时,需要修改所有的状态类(通过我们的优化已经避免了这种情况)
- 状态的转换分散在不同的子类中,学习成本较高
应用场景
- 听到状态这个词时
- 有时候状态是内在的,需要我们识别出来,譬如在有很多的if 判断时,很可能就是内在的状态转换。
来源:oschina
链接:https://my.oschina.net/liufq/blog/3209188