状态模式:主要用来解决对象在多种状态转换时需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像改变了其类。
举例:人的一生。小的时候(假设是0~7岁)只能当幼儿园学生;青年时(假设是7 ~ 18岁)可以当初中和高中生,相应可以做的事情也变多了;成年时,可以做大学生,硕士生等等,做的事情变得更多了。
- 状态模式的关键是将对象的状态封装称为独立的类,对象调用方法时,可以委托当前对象所具有的状态调用相应的方法,是当前对象看起来好像是修改了它的类。
角色说明:
- 环境( Context ):环境是一个类,该类含有抽象状态声明的变量,可以引用任何具体状态类的实例。用户对该环境类的实例在某种状态下的行为感兴趣。
- 抽象状态( State ):抽象状态是一个接口或抽象类。抽象状态中定义了与环境的一个特定状态相关的若干个方法。
- 具体状态( Concrete State ):具体状态是实现( 扩展 )抽象状态( 抽象类 )的类。
“普通” 状态模式案例
Thermometer.java
public class Thermometer { // 环境
TemperatureState state;
public void showMessage() {
System.out.println("*********");
state.showTemperature();
System.out.println("*********");
}
public void setState(TemperatureState state) {
this.state = state;
}
}
TemperatureState.java
public interface TemperatureState { // 抽象状态
public void showTemperature();
}
LowState.java
public class LowState implements TemperatureState{ // 具体状态
double n = 0;
public LowState(double n) {
if(n<=0) {
this.n=n;
}
}
@Override
public void showTemperature() {
System.out.println("现在的温度是"+n+"属于低温度");
}
}
MiddleState.java
public class MiddleState implements TemperatureState{ // 具体状态
double n = 15;
public MiddleState(double n) {
if(n>0&&n<26) {
this.n=n;
}
}
@Override
public void showTemperature() {
System.out.println("现在的温度是"+n+"属于低温度");
}
}
HeightState.java
public class HeightState implements TemperatureState{ // 具体状态
double n = 15;
public HeightState(double n) {
if(n>=39) {
this.n=n;
}
}
@Override
public void showTemperature() {
System.out.println("现在的温度是"+n+"属于低温度");
}
}
Application4.java
public class Application4 {
public static void main(String[] args) {
Thermometer thermometer = new Thermometer();
thermometer.setState(new LowState(0));
thermometer.showMessage();
thermometer.setState(new MiddleState(15.0));
thermometer.showMessage();
thermometer.setState(new HeightState(40.0));
thermometer.showMessage();
}
}
运行结果:
结果将所有的状态变化的行为都展示了出来。
“状态切换”状态模式案例
Activity.java
public class Activity { // 环境, 这个例子属于 "状态切换" ,对应到课本上是p205页, 不是普通的状态模式
/**
* 与普通的状态模式相比,这个 "状态切换" 就是把状态切换的操作放在了程序内部实现,不用显示的展现给用户实现;
* 也就是说普通的状态模式会把切换状态的操作的过程和结果都展现出来给用户,这样是不符合生活实际的,体验感会及其差劲。
*
* */
State state = null; // 活动当前状态, 是可以变化的
int count = 0; // 奖品数量
// 四个属性表示四种状态
State noRaffleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseOutState = new DispenseOutState(this);
State dispenseState = new DispenseState(this);
// 初始化当前状态
public Activity(int count) {
this.state = getNoRaffleState(); // 初始化的状态是 不能抽奖状态
this.count = count;
}
// 扣分(执行每个状态的扣分政策)
public void deduceMoney() {
state.deduceMoney();
}
// 抽奖
public void raffle() {
if(state.raffle()) { // 满足条件就是抽奖成功
state.dispensePrize(); // 领取奖品
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public void setCount(int count) {
this.count = count;
}
public int getCount() {
int curCount = count;
count--; // 每领取一次奖品, 奖品数量减少一个
return curCount;
}
public State getNoRaffleState() {
return noRaffleState;
}
public void setNoRaffleState(State noRaffleState) {
this.noRaffleState = noRaffleState;
}
public State getCanRaffleState() {
return canRaffleState;
}
public void setCanRaffleState(State canRaffleState) {
this.canRaffleState = canRaffleState;
}
public State getDispenseOutState() {
return dispenseOutState;
}
public void setDispenseOutState(State dispenseOutState) {
this.dispenseOutState = dispenseOutState;
}
public State getDispenseState() {
return dispenseState;
}
public void setDispenseState(State dispenseState) {
this.dispenseState = dispenseState;
}
}
State.java
public interface State { // 抽象状态
public void deduceMoney(); // 扣除50积分
public boolean raffle(); // 是否抽中奖品
public void dispensePrize(); // 发放奖品
}
NoRaffleState.java
public class NoRaffleState implements State{ // 具体状态(不能抽奖状态), 当前状态可以扣除积分, 扣除后将状态设置为可以抽奖状态, 也就是当前状态不可以抽奖
//状态和行为是一一对应的,状态之间可以相互转换
Activity activity;
public NoRaffleState(Activity activity) {
this.activity=activity;
}
@Override
public void deduceMoney() {
System.out.println("扣除50积分成功,可以抽奖了");
activity.setState(activity.getCanRaffleState());
}
@Override
public boolean raffle() {
System.out.println("扣除积分之后才可以抽奖哦");
return false;
}
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}
CanRaffleState.java
public class CanRaffleState implements State{ // 具体状态(可以抽奖状态), 当前状态不可以扣分
Activity activity;
public CanRaffleState(Activity activity) {
this.activity=activity;
}
@Override
public void deduceMoney() {
System.out.println("以及扣除过积分了");
}
@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍等");
Random random = new Random();
int num = random.nextInt(11); // nextInt(int index) 返回伪随机的,均匀分布的, int值介于0(含)和指定值(不包括),从该随机数生成器的序列绘制
//10%中奖机会
if(num==0) {
activity.setState(activity.getDispenseState());
return true;
}else { // 90%未中奖机会
System.out.println("很遗憾没有抽中奖品");
activity.setState(activity.getNoRaffleState()); // 改变状态为不能抽奖
return false;
}
}
@Override
public void dispensePrize() {
System.out.println("未中奖, 不能发放奖品");
}
}
DispenseState.java
public class DispenseState implements State{ // 具体状态(发放奖品的状态)
Activity activity;
public DispenseState(Activity activity) {
this.activity=activity;
}
@Override
public void deduceMoney() {
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.getNoRaffleState()); // 改变状态为不能抽奖
}else {
System.out.println("奖品发放完了");
// activity.setState(activity.getDispenseOutState()); // 改变状态为奖品发送完毕, 后面我们不能抽奖了
System.exit(0); // 上一句代码可以替换为此,如果是这种方式,那么当没有奖品时就会直接退出系统不会执行之后没有意义(没有奖品)的抽奖活动
}
}
}
DispenseOutState.java
public class DispenseOutState implements State{ // 具体状态(奖品发放完毕状态), 此后抽奖活动结束了
Activity activity;
public DispenseOutState(Activity activity) {
this.activity=activity;
}
// 这个状态里面没有在对 State 进行任何的改变,所以一旦变为此状态就不会再变成其它状态,所以当奖品发放完时本就是本次抽奖活动结束,也就是此状态
@Override
public void deduceMoney() {
System.out.println("奖品发送完了, 请下次再参加");
}
@Override
public boolean raffle() {
System.out.println("奖品发送完了, 请下次再参加");
return false;
}
@Override
public void dispensePrize() {
System.out.println("奖品发送完了, 请下次再参加");
}
}
Application2.java
public class Application2 {
public static void main(String[] args) {
Activity activity = new Activity(1); // 创建活动对象,奖品池有一个奖品
//连续抽奖300次
for(int i=0;i<30;i++) {
System.out.println("--------第"+(i+1)+"次抽奖--------");
//参将抽奖, 第一步点击扣除积分
activity.deduceMoney();
//第二步抽奖
activity.raffle();
}
}
}
运行结果:
“状态切换” 与 “普通” 的状态模式相比,“切换” 就是把 “切换” 放在了类的内部,不用用户显示的实现。
状态切换解释说明:环境实例在某种状态下执行一个方法后,可能导致该实例的状态发生变化。程序通过使用状态模式可方便地将环境实例从一个状态切换为另一个状态。当一个环境实例有确定的若干个状态时,可以由环境实例本身负责状态的切换,该环境实例可以含有所有状态的引用,并提供设置改变状态的方法,比如setState(State state)方法。
状态模式优点:
- 代码有很强的可读性。
- 方便维护(删除容易产生问题的if-else语句)。
- 使用一个类封装对象的一种状态,很容易动态的增加新的状态。
- 在状态模式下。环境(context)中不必出现大量的条件判断语句。环境(context)实例所呈现的状态变得更加清晰,容易理解。
- 可以让用户程序很方便地切换环境(context)实例的状态。
- 不会让环境(context)的实例中出现内部状态不一致的情况。
- 当状态对象没有实例变量时,环境(context)的各个实例可以共享一个状态对象。
适合场景:
- 一个对象的行为依赖于它的状态,并且它必须在运行时根据状态改变它的行为。
- 需要编写大量的条件分支语句来决定一个操作的行为,而且这些条件恰好表示对象的一种状态。