设计模式之状态模式

让人想犯罪 __ 提交于 2020-03-23 17:27:16

3 月,跳不动了?>>>

定义

当一个对象的内部状态改变时允许改变其行为,这个对象看起来像是改变了其类。

由定义里可以看出,状态的改变是为了改变对象的行为,所以我们的思路就是将行为封装到对象中,然后利用多态机制来控制行为。

角色

状态模式有三个角色:

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 判断时,很可能就是内在的状态转换。
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!