前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式之状态模式

设计模式之状态模式

作者头像
Dylan Liu
发布2020-04-24 18:13:56
5670
发布2020-04-24 18:13:56
举报
文章被收录于专栏:dylanliudylanliudylanliu

定义

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

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

角色

状态模式有三个角色:

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 判断时,很可能就是内在的状态转换。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义
  • 角色
  • 模式说明
  • 优点
  • 缺点
  • 应用场景
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档