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