简介

状态模式:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

简单点来说,我们在平时开发的过程中,会遇到一个对象中会判断一个“状态”的不同,从而会使用不同的行为(函数之类的调用),常见的做法就是if else或者switch这种判断方式,当我们一个状态有多种形式时,可能会产生大量的if else语句,这就不好维护了,如果每次新增一种状态,就得改动一次该对象,显然不符合我们的开闭原则。

而状态模式是将每种状态的处理都单独提取出来,然后通过源对象进行组合使用,状态对象会持有源对象,当状态A发生改变时,会调用持有的源对象的设置状态方法,将下一个状态对象设置进去,这个状态对象常见的做法就是从源对象里面获取。

但是你会发现,当我们新增一个状态时,源对象还是要改动的,只不过没有大量的if else判断,可读性更高了,所以状态模式对开闭原则支持的并不是太好,如果可以的话,我的想法是状态对象有一个接口,大家都去依赖它,而源对象不要组合进来所有的状态,而是通过函数接参进来,这样的话,新增一个状态,只需要改动状态对象,而不用改动源对象。

角色:

  1. 环境角色:源对象,它维护着一份状态,并且提供状态切换的接口和其他客户需要的api
  2. 抽象状态角色:定义接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  3. 具体状态角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

代码实现

我们模拟一个聊天状态,我们有:在线、离线、繁忙三种状态,在线时可以正常接受到消息,而离线接受不到消息,繁忙则是一个会携带自动回复。

//抽象状态
abstract class AbstractState {
  private tim: Tim; //环境

  constructor(tim: Tim) {
    this.tim = tim;
  }

  //接收消息
  public abstract receivesMessage(message: string): string | boolean;
}

//在线
class Online extends AbstractState {
  constructor(tim: Tim) {
    super(tim);
  }

  public receivesMessage(message: string): boolean {
    console.log(`成功接收到消息:${message}`);
    return true;
  }
}

//离线
class Offline extends AbstractState {
  constructor(tim: Tim) {
    super(tim);
  }

  public receivesMessage(message: string): boolean {
    console.log(`消息接收失败,对方已离线,消息:${message}`);
    return false;
  }
}

//繁忙
class busy extends AbstractState {
  constructor(tim: Tim) {
    super(tim);
  }

  public receivesMessage(message: string): string {
    console.log(`对方繁忙,消息已发送给对方,消息:${message}`);
    return `你好,我现在不在电脑前,稍后会给你回复`;
  }
}

//环境
class Tim {
  private state: AbstractState;

  constructor() {
    //设置默认状态
    this.state = new Online(this);
  }

  public setState(state: AbstractState): void {
    this.state = state;
  }

  public receivesMessage(message: string): string | boolean {
    return this.state.receivesMessage(message);
  }
}

//实际使用
class Client {
  constructor() {
    //创建tim聊天
    const tim = new Tim();

    //接收消息
    tim.receivesMessage("你好");

    //断网了
    tim.setState(new Offline(tim));

    //接收消息
    tim.receivesMessage("你好");

    //繁忙了
    tim.setState(new busy(tim));

    //接收消息
    const res = tim.receivesMessage("你好");
    console.log(res);
  }
}

new Client();

状态的设置其实可以再很多地方设置,比如订单的状态:

下单 --> 结算 --> 付款成功 --> 发货 --> 确认收货 --> 订单完成;

我们可以在每个状态里去监听自己的需要的东西,比如结算里面去监听用户是否支付成功,如果支付成功,可以再状态里去改状态,因为状态持有了环境对象的实例,可以直接去设置新的状态,不一定是外部去操控。

这样的话状态与状态进行关联,解耦了部分代码。

但其实状态对象在真实业务中会承载很多接口(函数),因为下单的状态可能有一个获取所有物品的接口,结算时会有计算价格的接口,付款时返回需要付款的金额的接口,等等,但是不同的状态之间,有些接口是不能处理的,所有往往大家都有一份完整接口,但是没有具体的实现,比如:

public class AbstractOrderStatus implements OrderEvent {

    @Override
    public void orderTimeout(OrderStatusContext context) {
        throw new ServiceErrorException("目前订单状态不支持该流转");
    }

    @Override
    public void paySuccess(OrderStatusContext context) {
        throw new ServiceErrorException("目前订单状态不支持该流转");
    }

    @Override
    public void startServe(OrderStatusContext context) {
        throw new ServiceErrorException("目前订单状态不支持该流转");
    }

    ...
}

大家只处理自己状态的具体内容就行了,其他都是空函数这种,报个错啥的。

应用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

拓展

状态模式与责任链模式的区别

状态模式和责任链模式都能消除 if-else 分支过多的问题。但在某些情况下,状态模式中的状态可以理解为责任,那么在这种情况下,两种模式都可以使用。

从定义来看,状态模式强调的是一个对象内在状态的改变,而责任链模式强调的是外部节点对象间的改变。

从代码实现上来看,两者最大的区别就是状态模式的各个状态对象知道自己要进入的下一个状态对象,而责任链模式并不清楚其下一个节点处理对象,因为链式组装由客户端负责。

状态模式与策略模式的区别

状态模式和策略模式的 UML 类图架构几乎完全一样,但两者的应用场景是不一样的。策略模式的多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法,而状态模式的各个状态间存在相互关系,彼此之间在一定条件下存在自动切换状态的效果,并且用户无法指定状态,只能设置初始状态。

分类: 设计模式 标签: 设计模式状态模式

评论

暂无评论数据

暂无评论数据

目录