设计模式——状态模式

本文介绍了设计模式中的状态模式,通过糖果机的例子详细解释了如何使用状态模式来处理对象状态变化带来的行为变化。文章阐述了状态模式的定义、工作原理以及如何通过创建状态接口和具体状态类实现状态的封装,减少了代码的复杂性和维护难度。同时,提到了状态模式的优点,如行为局部化、易于扩展等,并给出了状态模式的类图。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

状态模式

 本篇是《HeaderFirst设计模式》读书笔记。


状态模式定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

 

不知道你看了定义有啥感悟没,反正我看了是不知道在说啥。下面从实际例子出发

 

我们要制作一个糖果机。糖果机的控制器的工作如下图:

 

 

我们把这张图的每个圆圈看成一个状态,每个箭头看成状态的转换。所以根据图片,我们能找到的四个状态:没有25美分、有25美分、糖果售罄、售出糖果;改变状态的动作:投入25分钱、退回25分钱、转动曲柄、发放糖果。没一个动作都会造成糖果机状态的改变。

 

在这里我们使用一个通用技巧:如何对对象内的状态建模——通过创建一个实例变量来持有状态值,并在方法内书写条件代码来处理不同状态。

 

糖果机代码编写:

我们利用实例变量来持有当前的状态,然后需要处理所有可能发生的动作、行为和状态的转换。

public class GumballMachine {
   // 每一个状态都用一个不同的整数代表
   final static int SALD_OUT = 0,
         NO_QUARTER = 1,
         HAS_QUARTER = 2,
         SOLD = 3;
  
   // 持有当前的状态。因为糖果机一开始拆箱并安装的时候是没有糖果的,所以我们将它设为“糖果售罄”状态
   int state = SALD_OUT;
  
  
   // 追踪糖果机内的糖果数目
   int count = 0;
  
   public GumballMachine(int count) {
      this.count = count;
      // 如果糖果数量大于0,进入“没有硬币”状态
      if(count > 0) state = NO_QUARTER;
   }
  
   // 投入硬币
   public void insertQuarter() {
      if(state == SALD_OUT) {
         System.out.println("You can't insert a quarter, the machine is sold out.");
      } else if(state == NO_QUARTER) {
         state = HAS_QUARTER;
         System.out.println("you inserted a quarter");
      } else if(state == HAS_QUARTER) {
         System.out.println("You can't insert another quarter");
      } else if(state == SOLD) {
         // 如果顾客刚刚才买了糖果,就需要稍等一下,好让状态转换完毕,回复到“没有硬币”的状态
         System.out.println("Please wait, we're already giving you a gumball");
      }
   }
  
   // 退回硬币
   public void ejectQuarter() {
      if(state == SALD_OUT) {
         System.out.println("You can't eject, you haven't inserted a quarter yet");
      } else if(state == NO_QUARTER) {
         System.out.println("You haven't inserted a quarter");
      } else if(state == HAS_QUARTER) {
         state = NO_QUARTER;
         System.out.println("Quarter returned");
      } else if(state == SOLD) {
         System.out.println("Sorry, you already turned the crank");
      }
   }
  
   // 转动曲柄
   public void turnCrank() {
      if(state == SALD_OUT) {
         System.out.println("You turned, but there are no gumballs");
      } else if(state == NO_QUARTER) {
         System.out.println("You turned but there's no quarter");
      } else if(state == HAS_QUARTER) {
         // 改变状态到“售出糖果”,然后调用机器的dispense()方法。
         state = SOLD;
         System.out.println("You turned...");
         dispense();
      } else if(state == SOLD) {
         System.out.println("Turning twice doesn't get you anoter gumball!");
      }
   }
  
   // 发放糖果
   public void dispense() {
      if(state == SALD_OUT) {
         System.out.println("No gumball dispensed");
      } else if(state == NO_QUARTER) {
         System.out.println("You need to pay first");
      } else if(state == HAS_QUARTER) {
         System.out.println("No gumball dispensed");
      } else if(state == SOLD) {
         System.out.println("A gumball comes rolling out the slot");
         count --;
         if(count == 0){
            System.out.println("Oops, out of gumblls!");
            state = SALD_OUT;
         } else {
            state = NO_QUARTER;
         }
      }
   }
  
}


现在我们在这个糖果机的基础上加功能:10人有1人可以得到免费的糖果。加入此功能后糖果机的工作状态图如下: 


 

如果对上面的代码进行修改,你会发现要改的东西特别多乱杂,首先要加一个新的状态,其次要在每个方法中接入一个新的条件判断来处理“赢家”状态。如果随着糖果机优化,需要加入越来越多状态的话,那么就需要对这段代码不断的修改增添,程序中bug的几率就会整大。

 

所以我们就要改动啦,不维护现有的代码,重写它以便于将状态对象封装在各自的类中,然后在动作发生时委托给当前的状态。

 

1、  首先,我们定义一个State接口。在这个接口内,糖果机的每个动作都有一个对应的方法。

2、  然后为机器中的每个状态实现状态类。这些类将负责在对应的状态下进行机器行为。

3、  最后,我们要摆脱旧的条件代码,取而代之的方式是,将动作委托到状态类。

 

实现状态接口:

首先,让我们创建一个State接口,所有的状态都必须实现这个接口:

/**
 * 所有状态的接口。这些方法直接映射到糖果机上可能发生的动作。
 * @author Administrator
 */
public interface State {
   public void insertQuarter();
   void ejectQuarter();
   void turnCrank();
   void dispense();
}

实现状态类: 

我们先从NoQuarterState开始。

public class NoQuarterState implements State {
  
   GumballMachine gumballMachine;
 
   public NoQuarterState(com.bajie.status1.GumballMachine gumballMachine) {
      this.gumballMachine = gumballMachine;
   }
 
   @Override
   public void insertQuarter() {
      System.out.println("you inserted a quarter");
      gumballMachine.setState(gumballMachine.hasQuarterState);
   }
 
   @Override
   public void ejectQuarter() {
      System.out.println("You haven't inserted a quarter");
   }
 
   @Override
   public void turnCrank() {
      System.out.println("You turned but there's no quarter");
   }
 
   @Override
   public void dispense() {
      System.out.println("You need to pay first");
   }
 
}


HasQuarterState: 

public class HasQuarterState implements State {
  
   GumballMachine gumballMachine;
 
   public HasQuarterState(com.bajie.status1.GumballMachine gumballMachine) {
      this.gumballMachine = gumballMachine;
   }
 
   @Override
   public void insertQuarter() {
      System.out.println("You can't insert another quarter");
   }
 
   @Override
   public void ejectQuarter() {
      System.out.println("Quarter returned");
      gumballMachine.setState(gumballMachine.getNoQuarterState());
   }
 
   /* (non-Javadoc)
    * @see com.bajie.status1.State#turnCrank()
    */
   @Override
   public void turnCrank() {
      System.out.println("You turned...");
      gumballMachine.setState(gumballMachine.getSoleState());
   }
 
   @Override
   public void dispense() {
      System.out.println("No gumball dispensed");
   }
 
}

其他状态类与此类相似。 

 

重新改造的糖果机

public class GumballMachine {
   // 糖果机的四个状态
   State soldOutState,
      noQuarterState,
      hasQuarterState,
      soleState,
      winnerState;
  
   // 跟踪糖果机的当前状态
   State state = soldOutState;
   // 追踪糖果机内的糖果数目
   int count = 0;
  
   public GumballMachine(int count) {
      soldOutState = new SoldOutState(this);
      noQuarterState = new NoQuarterState(this);
      hasQuarterState = new HasQuarterState(this);
      soleState = new SoldState(this);
      winnerState = new WinnerState(this);
     
      this.count = count;
      // 如果糖果数量大于0,进入“没有硬币”状态
      if(count > 0) state = noQuarterState;
   }
  
   // 投入硬币
   public void insertQuarter() {
      state.insertQuarter();
   }
  
   // 退回硬币
   public void ejectQuarter() {
      state.ejectQuarter();
   }
  
  
   /**
    * 我们不需要在GumballMachine中准备一个dispense()的动作方法,因为这只是一个内部的动作;
    * 用户不可以直接要求机器发送糖果。
    * 但我们是在状态对象的turnCrank()方法中调用dispense()方法的。
    */
   // 转动曲柄
   public void turnCrank() {
      state.turnCrank();
      // 发放糖果
      state.dispense();
     
   }
  
   void releaseBall() {
      System.out.println("A gumball comes rolling out the slot...");
      if(count != 0) count --;
   }
 
  
   public void setState(State state) {
      this.state = state;
   }
 
   public State getSoldOutState() {
      return soldOutState;
   }
 
   public void setSoldOutState(State soldOutState) {
      this.soldOutState = soldOutState;
   }
 
   public void setNoQuarterState(State noQuarterState) {
      this.noQuarterState = noQuarterState;
   }
 
   public State getHasQuarterState() {
      return hasQuarterState;
   }
 
   public void setHasQuarterState(State hasQuarterState) {
      this.hasQuarterState = hasQuarterState;
   }
 
   public State getSoleState() {
      return soleState;
   }
 
   public void setSoleState(State soleState) {
      this.soleState = soleState;
   }
 
   public int getCount() {
      return count;
   }
 
   public void setCount(int count) {
      this.count = count;
   }
 
   public State getState() {
      return state;
   }
 
   public State getNoQuarterState() {
      return noQuarterState;
   }
 
   public State getWinnerState() {
      return winnerState;
   }
 
   public void setWinnerState(State winnerState) {
      this.winnerState = winnerState;
   }
  
}

至此我们已经实现了状态模式,如果我们需要再加入一个状态就易如反掌了。 

加入一个WinnerState状态类:

public class WinnerState implements State {
 
   GumballMachine gumballMachine;
 
   public WinnerState(com.bajie.status1.GumballMachine gumballMachine) {
      this.gumballMachine = gumballMachine;
   }
 
   @Override
   public void insertQuarter() {
      System.out.println("Please wait, we're already giving you a gumball");
   }
 
   @Override
   public void ejectQuarter() {
      System.out.println("Sorry, you already turned the crank");
   }
 
   @Override
   public void turnCrank() {
      System.out.println("Turning twice doesn't get you anoter gumball!");
   }
 
   @Override
   public void dispense() {
      System.out.println("YOU'RE A WINNER! You get two gunballs for your quarter");
      gumballMachine.releaseBall();
     
      if(gumballMachine.getCount() == 0) {
         gumballMachine.setState(gumballMachine.getSoldOutState());
      } else {
        gumballMachine.releaseBall();
        if(gumballMachine.getCount() > 0) {
           gumballMachine.setState(gumballMachine.getNoQuarterState());
        } else {
           gumballMachine.setState(gumballMachine.getSoldOutState());
           System.out.println("Oops, out of gumballs!");
        }
      }
     
   }
 
}

然后稍微改造一下HasQuarterState的代码:

import java.util.Random;
 
public class HasQuarterState implements State {
  
   Random randomWinner = new Random(System.currentTimeMillis());
  
   GumballMachine gumballMachine;
 
   public HasQuarterState(com.bajie.status1.GumballMachine gumballMachine) {
      this.gumballMachine = gumballMachine;
   }
 
   @Override
   public void insertQuarter() {
      System.out.println("You can't insert another quarter");
   }
 
   @Override
   public void ejectQuarter() {
      System.out.println("Quarter returned");
      gumballMachine.setState(gumballMachine.getNoQuarterState());
   }
 
   /* (non-Javadoc)
    * @see com.bajie.status1.State#turnCrank()
    */
   @Override
   public void turnCrank() {
      System.out.println("You turned...");
      int random = randomWinner.nextInt(10);
      if(random == 0 && gumballMachine.getCount() > 1) {
        gumballMachine.setState(gumballMachine.winnerState);
      } else {
         gumballMachine.setState(gumballMachine.getSoleState());
      }
   }
   @Override
   public void dispense() {
      System.out.println("No gumball dispensed");
   }
}

下面运行我们的糖果机:

public class Main {
 
   public static void main(String[] args) {
     
      GumballMachine gumballMachine = new GumballMachine(5);
      gumballMachine.insertQuarter();
      gumballMachine.turnCrank();
      gumballMachine.insertQuarter();
      gumballMachine.turnCrank();
      gumballMachine.insertQuarter();
      gumballMachine.turnCrank();
      gumballMachine.insertQuarter();
      gumballMachine.turnCrank();
      gumballMachine.insertQuarter();
      gumballMachine.turnCrank();
 
   }
 
}

和之前的糖果机相比,我们做了一下几点:

1、  将每个状态的行为局部化到它自己的类中。

2、  将容易产生问题的if语句删除,以方便日后的维护

3、  让每一个状态“对修改关闭”,让糖果机“对扩展开放”,因为可以加入新的状态类。

4、  创建一个新的代码基和类结构,这更能映射万能糖果机的图,而且更容易阅读和理解。

 

接下来给出状态模式的类图:


 

最后总结下状态模式的要点:

1、  状态模式允许一个对象基于内部状态而拥有不同的行为。

2、  状态模式用类代表状态

3、  Context会将行为委托给当前状态对象

4、  通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了

5、  状态模式通常会用行为或算法来配置Context类

6、  状态模式允许Context随着状态的改变而改变行为

7、  状态转换可以由State类或Context类控制

8、  使用状态模式通常会导致设计中类的数目大量增加

9、  状态类可以被多个Context实例共享。












                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值