状态模式

定义

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

使用场景

  • 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为
  • 代码中包含大量与对象状态有关的条件语句,例如,一个操作中含有庞大的多分支语句(if - else 或 switch - case),并且这些分支依赖于该对象的状态

结构

  • Context环境类

定义客户感兴趣的接口,维护一个State子类的实例,这个实例定义了当前的状态

  • State 抽象状态类

    抽象状态类或者接口,定义了一个或者一组接口,表示该状态下的行为

  • ConcreteState具体状态类

    具体实现类,具体实现状态抽象类State中定义的接口,从而达到不同状态下的不同行为

状态模式的结构与策略模式机构几乎完全一样,但它们的目的、本质完全不一样。策略模式的行为是彼此独立的、可以相互替换的;状态模式是把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共通的抽象状态基类
通俗来说,策略模式适用于我知道要去干什么事情,但是我拿不定主义要用什么方法去做,比如要出行了,我要计算消耗的时间,我还不能确定是乘航班好呢、还是坐高铁好呢;而状态模式则适用于我已经知道我要干什么,我也知道要做的事情会有那些状态,根据不同的状态我需做不同的事情,比如在app的用户登录体系中,用户存在登录状态、未登录状态,在这两种状态下发起一个聊天操作,登录状态下的用户可以直接进入聊天,未登录状态下的用户需要先跳到登录界面,这里就可以使用状态模式。

简单实现

积分 发帖 回复 下载 等级
<100 Y Y N 少侠
<1000 Y 获得积分翻倍 Y Y 消耗积分 大侠
>=1000 Y 获得积分翻倍 Y Y 消耗积分减半 宗师

模拟一个简单社群回复操作,回复和发帖能获得积分,积分可以用来下载,根据不同的积分等级,用户有不同的权限和称号。

在不使用状态模式之前,我们进行任何一个操作的时候,可能都要对当前积分进行if-else判断,然后根据当前等级执行相应的具体操作,操作完成后我们又得用if-else之类语句来判断现有等级是否发生了变动。

假设一个用户先发了帖,然后去下载,下载发现有问题,然后去留言,留言得到反馈后,继续进行下载。如果不使用状态模式之前,你会怎么写?

思考一下,如果我能在执行具体操作的时候不用去关心这个对象内部到底是处于什么状态,我只需要知道能不能执行,以及执行后的结果,那问题不就简单很多了吗。而这,完全可以通过状态模式办到。

现在来简单实现一下,首先需要抽象出各种状态接口,这里无非就是下载、回复、发帖,所以我们将这3个接口抽到一个接口类中。

public interface State {

  /**
   * 下载
   *
   * @param score 下载需要消耗的积分
   */
  void downLoadFile(int score);

  /**
   * 发帖
   *
   * @param score 发帖获得的奖励积分
   */
  void posted(int score);

  /**
   * 回复留言
   *
   * @param score 回帖的奖励积分
   */
  void reply(int score);

  /**
   * 判断当前状态
   *
   * @param score 执行某个操作涉及到的积分
   */
  void checkState(int score);
}

你会发现,除了上面说的3个接口外,发现还多了一个checkState接口,该接口是用来判断当前状态的,这个接口比较重要,是用来对状态变更进行具体实现的接口。为了方便我就直接放在State里面了,你也可以把它单独抽出来,实现一个controller来管理各种类型的变更。

public class ForumAccount {

  private volatile int mPoint;
  private String userName;
  private State mState;
  private String stateName;

  public ForumAccount(String name) {
    this.userName = name;
    mState = new PrimaryState(this);
  }

  public void setState(State state) {
    mState = state;
  }

  public void downLoadFile(int score) {
    mState.downLoadFile(score);
  }

  public void posted(int score) {
    mState.posted(score);
  }

  public void reply(int score) {
    mState.reply(score);
  }

  /**
   * 获得当前积分
   */
  public int getPoint() {
    return mPoint;
  }

  public void setPoint(int point) {
    mPoint = point;
  }

  public String getName() {
    return userName;
  }

  public String getStateName() {
    return stateName;
  }

  public void setStateName(String stateName) {
    this.stateName = stateName;
  }
}

帐号管理类,该类充当了Content的角色,默认State为初级,即少侠类,并且分别提供了downLoadFile()用于执行下载操作,posted()用于执行发帖操作,reply()用于执行回帖操作。在里面包含一个State类,并且提供了set方法来切换State状态。

public class PrimaryState implements State {

  int point;
  ForumAccount mAccount;

  public PrimaryState(ForumAccount account) {
    account.setStateName("少侠");
    this.point = account.getPoint();
    this.mAccount = account;
  }

  @Override public void downLoadFile(int score) {
    System.out.println("对不起," + mAccount.getName() + ",您没有下载文件的权限!");
  }

  @Override public void posted(int score) {
    System.out.println(mAccount.getName() + "发布留言,增加" + score + "积分");
    this.point += score;
    checkState(score);
    System.out.println("剩余积分为" + mAccount.getPoint() + " 当前级别为" + mAccount.getStateName());
  }

  @Override public void reply(int score) {
    System.out.println(mAccount.getName() + " 回复留言,增加" + score + "积分");
    this.point += score;
    checkState(score);
    System.out.println("剩余积分为:" + mAccount.getPoint() + " 当前级别为" + mAccount.getStateName());
  }

  @Override public void checkState(int score) {
    mAccount.setPoint(point);
    if (point >= 1000) {
      mAccount.setState(new HighState(mAccount));
    } else if (point >= 100) {
      mAccount.setState(new MiddleState(mAccount));
    }

  }
}

少侠状态的实现类,对少侠能干的事情进行具体定义

public class MiddleState implements State {

  int point;
  ForumAccount mAccount;

  public MiddleState(ForumAccount account) {
    account.setStateName("大侠");
    this.point = account.getPoint();
    this.mAccount = account;
  }

  @Override public void downLoadFile(int score) {

    System.out.println(mAccount.getName() + "要下载文件,扣除" + score + "积分");
    this.point -= score;
    checkState(score);
    System.out.println("剩余积分为" + point + ",当前等级为" + mAccount.getStateName());
  }

  @Override public void posted(int score) {
    System.out.println(mAccount.getName() + "发布新帖,增加" + score + "*2 积分");
    this.point += score * 2;
    checkState(score * 2);
    System.out.println("剩余积分为" + mAccount.getPoint() + " , 当前级别为" + mAccount.getStateName());
  }

  @Override public void reply(int score) {
    System.out.println(mAccount.getName() + "发布留言,增加" + score + "积分");
    this.point += score;
    checkState(score);
    System.out.println("剩余积分为" + mAccount.getPoint() + ", 当前级别为" + mAccount.getStateName());
  }

  @Override public void checkState(int score) {
    mAccount.setPoint(point);
    if (point < 0) {
      System.out.println("积分不足,下载文件失败!");
      this.point += score;
      mAccount.setPoint(point);
    } else if (point < 100) {
      mAccount.setState(new PrimaryState(mAccount));
    } else if (point > 1000) {
      mAccount.setState(new HighState(mAccount));
    }
  }
}

大侠状态的实现类,对大侠能干的事情进行具体定义

public class HighState implements State {
  int point;
  ForumAccount mAccount;

  public HighState(ForumAccount account) {
    account.setStateName("宗师");
    this.point = account.getPoint();
    this.mAccount = account;
  }

  @Override public void downLoadFile(int score) {
    System.out.println(mAccount.getName() + "要下载文件,扣除" + score + "一半的积分");
    this.point -= score / 2;
    checkState(score / 2);
    System.out.println("剩余积分为" + point + ",当前等级为" + mAccount.getStateName());
  }

  @Override public void posted(int score) {
    System.out.println(mAccount.getName() + "发布新帖,增加" + score + "*2 积分");
    this.point += score * 2;
    checkState(score);
    System.out.println("剩余积分为" + mAccount.getPoint() + ", 当前级别为" + mAccount.getStateName());
  }

  @Override public void reply(int score) {
    System.out.println(mAccount.getName() + "发布留言,增加" + score + " 积分");
    this.point += score;
    checkState(score);
    System.out.println("剩余积分为" + mAccount.getPoint() + ", 当前级别为" + mAccount.getStateName());
  }

  @Override public void checkState(int score) {
    mAccount.setPoint(point);
    if (point < 0) {
      System.out.println("积分不足,下载文件失败!");
      this.point += score;
      mAccount.setPoint(point);
    } else if (point < 100) {
      mAccount.setState(new PrimaryState(mAccount));
    } else if (point < 1000) {
      mAccount.setState(new MiddleState(mAccount));
    }
  }
}

宗师状态的实现类,对宗师能干的事情进行具体定义

测试类与测试结果

public class Test {
  public static void main(String[] args) {
    ForumAccount account = new ForumAccount("杨过");
    account.downLoadFile(10);
    account.posted(20);
    account.downLoadFile(3);
    account.reply(33);
    account.posted(80);
    account.downLoadFile(3);
    account.downLoadFile(1700);
    account.reply(10);
    account.posted(10);
    account.downLoadFile(7);
    account.posted(700);
    account.reply(50);
    account.downLoadFile(200);
    account.downLoadFile(2000);
  }
}

对不起,杨过,您没有下载文件的权限!
杨过发布留言,增加20积分
剩余积分为20 当前级别为少侠
对不起,杨过,您没有下载文件的权限!
杨过 回复留言,增加33积分
剩余积分为:53 当前级别为少侠
杨过发布留言,增加80积分
剩余积分为133 当前级别为大侠
杨过要下载文件,需扣除3积分
剩余积分为130,当前等级为大侠
杨过要下载文件,需扣除1700积分
积分不足,下载文件失败!
剩余积分为130,当前等级为大侠
杨过发布留言,增加10积分
剩余积分为140, 当前级别为大侠
杨过发布新帖,增加10*2 积分
剩余积分为160 , 当前级别为大侠
杨过要下载文件,需扣除7积分
剩余积分为153,当前等级为大侠
杨过发布新帖,增加700*2 积分
剩余积分为1553 , 当前级别为宗师
杨过发布留言,增加50 积分
剩余积分为1603, 当前级别为宗师
杨过下载文件,扣除200一半的积分
剩余积分为1503,当前等级为宗师
杨过下载文件,扣除2000一半的积分
剩余积分为503,当前等级为大侠

小结

状态模式的主要优点在于封装了转换规则,它将所以与某个状态有关的行为放到一个类中,并可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为,还可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数

你可能感兴趣的:(状态模式)