设计模式——状态模式
更多内容,前往 IT-BLOG
在现实生活中,常常会出现这样的事例:一个人的情绪高兴时,会做出一些助人为乐的事情。情绪低落的时候,会做出一些伤天害理的事情。这里的情绪就是状态,对应做的事情就是行为。在软件开发中也是类似的,有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。
一、基本介绍
【1】状态(State)模式的定义:对有状态的对象,把复杂的 “判断逻辑” 提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
【2】状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为问题。状态和行为是一一对应的,状态之间可以相互转换。
【3】当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。
【4】这种类型的设计模式属于行为型模式。
二、状态模式的结构
状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。状态模式包含以下主要角色:
【1】环境(Context)角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
【2】抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
【3】具体状态(Concrete State)角色:实现抽象状态所对应的行为。
三、状态模式的应用案例
使用状态模式解决 APP 抽奖问题:根据如下流程中的状态,完成具体的业务操作。
【1】应用的结构类图:精华在RaffleActive(上下文类)和状态的子类中。两者之间相互组合,减少复杂的逻辑判断。
【2】State 接口的实现如下:
1 /* 2 * 状态对应的抽象行为 3 */ 4 public interface State { 5 //扣除积分 6 public void deduceMoney(); 7 //是否中奖 8 public boolean raffle(); 9 //发放奖品 10 public void dispensePrize(); 11 }
【3】State 子类一:扣除积分类 NoRaffleState 的实现如下,扣除成功后,将 state 设置为抽奖状态。
1 public class NoRaffleState implements State{ 2 3 //初始化时传入活动引用,扣除积分后改变其状态 4 RaffleActivity active; 5 6 //构造器 7 public NoRaffleState(RaffleActivity active) { 8 this.active = active; 9 } 10 11 // 当前状态可以扣分,扣分后修改状态 12 @Override 13 public void deduceMoney() { 14 System.out.println("扣除5个积分"); 15 active.setState(active.getCanRaffeState()); 16 17 } 18 @Override 19 public boolean raffle() { 20 System.out.println("抽了积分才能抽奖"); 21 return false; 22 } 23 24 @Override 25 public void dispensePrize() { 26 System.out.println("不能方法奖品"); 27 } 28 }
【4】 State 子类一:抽奖状态类 CanRaffeState,如果抽中设置状态为抽中状态,未抽中时设置状态为扣积分状态。
1 public class CanRaffeState implements State{ 2 RaffleActivity active; 3 4 //构造器 5 public CanRaffeState(RaffleActivity active) { 6 this.active = active; 7 } 8 9 //扣积分 10 @Override 11 public void deduceMoney() { 12 System.out.println("已扣除积分"); 13 } 14 15 @Override 16 public boolean raffle() { 17 System.out.println("正在抽奖,请稍等"); 18 int num = new Random().nextInt(5); 19 //20% 的中奖机会,中了则返回true 20 if(num == 0) { 21 active.setState(active.getDispenseState()); 22 return true; 23 24 }else { 25 System.out.println("很遗憾没有抽中奖品"); 26 active.setState(active.getNoRaffleState()); 27 return false; 28 } 29 } 30 31 @Override 32 public void dispensePrize() { 33 System.out.println("抽奖中,不能发送奖品"); 34 } 35 }
【5】 State 子类一:抽中状态类 DispenseState,如果礼物未送完,则发送礼物,并设置状态为扣分状态。否则设置为礼物以发放完,且活动结束状态。
1 public class DispenseState implements State{ 2 RaffleActivity active; 3 4 //构造器 5 public DispenseState(RaffleActivity active) { 6 this.active = active; 7 } 8 9 @Override 10 public void deduceMoney() { 11 System.out.println("不能扣积分"); 12 } 13 14 @Override 15 public boolean raffle() { 16 System.out.println("不能抽奖"); 17 return false; 18 } 19 20 @Override 21 public void dispensePrize() { 22 if(active.getCount()>0) { 23 System.out.println("恭喜中奖了,奖品已发货"); 24 active.setState(active.getNoRaffleState()); 25 }else { 26 System.out.println("很遗憾,奖品已发完"); 27 active.setState(active.getDispenseOutState()); 28 } 29 } 30 }
【6】 State 子类一:礼物发放完,却活动结束状态类 DispenseOutState
1 public class DispenseOutState implements State{ 2 3 @Override 4 public void deduceMoney() { 5 System.out.println("活动结束"); 6 } 7 8 @Override 9 public boolean raffle() { 10 System.out.println("活动结束"); 11 return false; 12 } 13 14 @Override 15 public void dispensePrize() { 16 System.out.println("活动结束"); 17 System.exit(0); 18 } 19 20 }
【7】上下文类 RaffleActivity,主要存储用户的状态和礼物的总记录数等重要信息。并组合所有的状态子类,传入自身对象。
【8】客户端端调用类 Client,只需要调用上下文类,便可实现客户端的需求。
1 public class Client { 2 3 public static void main(String[] args) { 4 //为了演示方便,就定义只有一个奖品 5 RaffleActivity activity = new RaffleActivity(1); 6 for(int i=0;i<30;i++) { 7 System.out.println("======第"+i+"次,抽取奖品========"); 8 //扣积分 9 activity.deduceMoney(); 10 //抽奖 11 activity.raffle(); 12 } 13 } 14 }
四、状态模式的特点
状态模式的主要优点如下:
【1】状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
【2】减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
【3】有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
状态模式的主要缺点如下:
【1】状态模式的使用必然会增加系统的类与对象的个数。
【2】状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。