【1】概述

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。这个模式将状态封装成独立的类,并将动作委托到 代表当前状态的对象,我们知道行为会随着内部状态而改变。

一个对象“看起来好像修改了它的类”是什么意思呢?从客户的视角来看:如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象。

状态模式它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。

① UML类图

认真学习设计模式之状态模式(State Pattern)_抽象类

  • Context是一个类,它可以拥有一些内部状态。
  • State接口定义了一个所有具体状态的共同接口,任何状态都实现这个相同的接口,这样一来状态之间可以互相替换。
  • 不管在什么时候,只要有人调用Context的request()方法,它就会被委托到状态来处理。
  • ConcreteState处理来自Context的请求。每一个ConcreteState都提供了它自己对于请求的实现。所以,当Context改变状态时,行为也跟着改变。

② 状态模式与策略模式

以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此context的行为也会随着改变。但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。

而以策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最适当的策略对象。

一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很难。有了策略模式,你可以通过组合不同的对象来改变行为。

我们把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context内见到地改变状态对象来改变context的行为。

③ 谁决定状态转换的流向

Context或者ConcreteState都可以。

一般来讲,当状态转换是固定的时候,就适合放在Context中。然而,当转换是动态的时候,通常就会放在状态类中。

将状态放在状态类中的缺点是:状态类之间产生了依赖。

④ State是接口还是抽象类?

答:都可以。如果我们没有共同的功能可以放进抽象类中,就会使用接口。在你实现状态模式时,很可能想使用抽象类。这么一来,当你以后需要在抽象类中加入新的方法是就很容易,不需要打破具体状态的实现。

【2】应用案例分析

请编写程序完成APP 抽奖活动具体要求如下:

  • 假如每参加一次这个活动要扣除用户50 积分,中奖概率是10%
  • 奖品数量固定,抽完就不能抽奖
  • 活动有四个状态: 可以抽奖、不能抽奖、发放奖品和奖品领完
  • 活动的四个状态转换关系图

认真学习设计模式之状态模式(State Pattern)_抽象类_02
那么需要如何做呢?

  • 定义出一个接口(或抽象类)叫状态接口,每个状态都实现(继承)它。
  • 接口有扣除积分方法、抽奖方法、发放奖品方法

① 状态抽象类

/**
* 状态抽象类
*/
public abstract class State {
// 扣除积分 - 50
public abstract void deductMoney();

// 是否抽中奖品
public abstract boolean raffle();

// 发放奖品
public abstract void dispensePrize();

}

② 可以抽奖的状态

public class CanRaffleState extends State {

RaffleActivity activity;

public CanRaffleState(RaffleActivity activity) {
this.activity = activity;
}

//已经扣除了积分,不能再扣
@Override
public void deductMoney() {
System.out.println("已经扣取过了积分");
}

//可以抽奖, 抽完奖后,根据实际情况,改成新的状态
@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍等!");
Random r = new Random();
int num = r.nextInt(10);
// 10%中奖机会
if(num == 0){
// 改变活动状态为发放奖品 context
activity.setState(activity.getDispenseState());
return true;
}else{
System.out.println("很遗憾没有抽中奖品!");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
return false;
}
}

// 不能发放奖品
@Override
public void dispensePrize() {
System.out.println("没中奖,不能发放奖品");
}
}

③ 奖品发放完毕状态

/**
* 奖品发放完毕状态
* 说明,当我们activity 改变成 DispenseOutState, 抽奖活动结束
*/
public class DispenseOutState extends State {

// 初始化时传入活动引用
RaffleActivity activity;

public DispenseOutState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("奖品发送完了,请下次再参加");
}

@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加");
return false;
}

@Override
public void dispensePrize() {
System.out.println("奖品发送完了,请下次再参加");
}
}

④ 发放奖品的状态

public class DispenseState extends State {

// 初始化时传入活动引用,发放奖品后改变其状态
RaffleActivity activity;

public DispenseState(RaffleActivity activity) {
this.activity = activity;
}

@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}

@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}

//发放奖品
@Override
public void dispensePrize() {
if(activity.getCount() > 0){
System.out.println("恭喜中奖了");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
}else{
System.out.println("很遗憾,奖品发送完了");
// 改变状态为奖品发送完毕, 后面我们就不可以抽奖
activity.setState(activity.getDispensOutState());
//System.out.println("抽奖活动结束");
//System.exit(0);
}

}
}

⑤ 不能抽奖状态

public class NoRaffleState extends State {

// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;

public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}

// 当前状态可以扣积分 , 扣除后,将状态设置成可以抽奖状态
@Override
public void deductMoney() {
System.out.println("扣除50积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}

// 当前状态不能抽奖
@Override
public boolean raffle() {
System.out.println("扣了积分才能抽奖喔!");
return false;
}

// 当前状态不能发奖品
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}

⑥ 抽奖活动(Context)

public class RaffleActivity {

// state 表示活动当前的状态,是变化
State state = null;
// 奖品数量
int count = 0;

// 四个属性,表示四种状态
State noRafflleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);

State dispenseState = new DispenseState(this);
State dispensOutState = new DispenseOutState(this);

//构造器
//1. 初始化当前的状态为 noRafflleState(即不能抽奖的状态)
//2. 初始化奖品的数量
public RaffleActivity( int count) {
this.state = getNoRafflleState();
this.count = count;
}

//扣分, 调用当前状态的 deductMoney
public void debuctMoney(){
state.deductMoney();
}

//抽奖
public void raffle(){
// 如果当前的状态是抽奖成功
if(state.raffle()){
//领取奖品
state.dispensePrize();
}

}

public State getState() {
return state;
}

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

//这里请大家注意,每领取一次奖品,count--
public int getCount() {
int curCount = count;
count--;
return curCount;
}

public void setCount(int count) {
this.count = count;
}

public State getNoRafflleState() {
return noRafflleState;
}

public void setNoRafflleState(State noRafflleState) {
this.noRafflleState = noRafflleState;
}

public State getCanRaffleState() {
return canRaffleState;
}

public void setCanRaffleState(State canRaffleState) {
this.canRaffleState = canRaffleState;
}

public State getDispenseState() {
return dispenseState;
}

public void setDispenseState(State dispenseState) {
this.dispenseState = dispenseState;
}

public State getDispensOutState() {
return dispensOutState;
}

public void setDispensOutState(State dispensOutState) {
this.dispensOutState = dispensOutState;
}
}

⑦ 测试状态模式

public class ClientTest {

public static void main(String[] args) {
// TODO Auto-generated method stub
// 创建活动对象,奖品有1个奖品
RaffleActivity activity = new RaffleActivity(1);

// 我们连续抽300次奖
for (int i = 0; i < 30; i++) {
System.out.println("--------第" + (i + 1) + "次抽奖----------");
// 参加抽奖,第一步点击扣除积分
activity.debuctMoney();

// 第二步抽奖
activity.raffle();
}
}
}

UML类图如下:
认真学习设计模式之状态模式(State Pattern)_抽象类_03