设计模式第10式:状态模式

前言

我们遇到状态机模型,常常会理不清“状态”和“行为”的关系。状态模式就是专门解决这个应用场景的,它通过改变对象内部的状态来帮助对象控制自己的行为。

正文

1、先来看一个案例

我们选取《HeadFirst 设计模式》中的糖果售卖机案例。这就是常见的状态机模型。
在这里插入图片描述
首先我们找出所有的状态:上图中的4个圆圈。于是我们用4个常量来表示每个状态:

final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;

int state = SOLD_OUT; // 初始状态

然后我们将系统中可以发生的动作整理出来,每个动作都对应一个方法。下面这个方法就是投币动作对应的方法。方法里面对每一个可能出现的状态展示适当的行为。

public void insertQuarter() {
	if (state == HAS_QUARTER) {
		sout("不能再投币了");
	} else if (state == SOLD_OUT) {
		sout("卖完了");
	} else if (state == SOLD) {
		sout("已售出");
	} else if (state == NO_QUARTER) {
		state = HAS_QUARTER;
		sout("已投币");
	}
}

public void ejectQuarter() {
	if (state == HAS_QUARTER) {
		...
	} else if (state == SOLD_OUT) {
		...
	} else if (state == SOLD) {
		...
	} else if (state == NO_QUARTER) {
		...
	}
}

public void turnCrank() {
	if (state == HAS_QUARTER) {
		...
	} else if (state == SOLD_OUT) {
		...
	} else if (state == SOLD) {
		...
	} else if (state == NO_QUARTER) {
		...
	}
}

public void dispense() {
	if (state == HAS_QUARTER) {
		...
	} else if (state == SOLD_OUT) {
		...
	} else if (state == SOLD) {
		...
	} else if (state == NO_QUARTER) {
		...
	}
}

其他的动作方法类似,里面都有一大堆if else代码。当我们费尽九牛二虎之力写完后,要加入新的需求:当转动糖果机摇杆,有10%的几率掉下来2颗糖果。这种情况下完全失控了,每一个动作方法都要跟着修改,改出BUG的几率会大大增加。

2、如何优化

仍然遵循“封装变化”的原则,我们将每个状态的行为都放在各自的类中,那么每个状态只需要实现它自己的动作就可以了。这样,糖果机只需要将动作委托给当前的状态对象。

  • 首先,我们定义一个State接口,在这个接口中,糖果机的每个动作都有一个对应的方法;
  • 然后,为机器中的每个状态实现一个状态类,这些类将负责在对应状态下进行机器的行为;
  • 最后,我们将糖果机的动作委托给状态类。

在这里插入图片描述
我们尝试实现其中一个状态类,我们要做的是实现当前状态下恰当的行为:

public class NoQuarterState implements State {
	Machine machine;
	// 构造器拿到糖果机的引用
	public NoQuarterState(Machine machine) {
		this.machine = machine;
	}
	// 当前状态下触发动作的结果
	public void insertQuarter() {
		sout("已投币");
		// 触发糖果机改变状态
		machine.setState(machine.getHasQuarterState());
	}

	public void ejectQuarter() {
		sout("未投币");
	}
	
	public void turnCrank() {
		sout("未投币");
	}

	public void dispense() {
		sout("未投币");
	}
}

于是我们重新改造糖果机:

public class Machine {
	// 常量替换成4个状态类
	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;

	State state = soldOutState; // 当前状态
	// 构造方法初始化每种状态
	public Machine() {
		soldOutState = new SoldOutState(this);
		noQuarterState= new NoQuarterState(this);
		hasQuarterState= new HasQuarterState(this);
		soldState = new SoldState(this);
	}
	// 糖果机的动作均委托给当前状态类的动作方法
	public void insertQuarter() {
		soldState.insertQuarter();
	}

	public void ejectQuarter() {
		soldState.ejectQuarter();
	}
	
	public void turnCrank() {
		soldState.turnCrank();
	}

	public void dispense() {
		soldState.dispense();
	}
}

3、定义状态模式

上面已经实现了状态模式,我们来对其进行一个定义:

状态模式允许对象在内部状态改变时改变它自身的行为,对象看起来好像修改了它的类。
  • 这个模式将状态封装成独立的类,并将动作委托给当前状态对象
  • 从用户的视角来看,如果使用的对象能否完全改变自己的行为,那么这个对象应该是从别的类实例化而来的。然而,状态模式仅通过组合不同的状态对象来造成改变类行为的假象。

我们来定义下状态模式的类图:
在这里插入图片描述

4、状态模式 vs 策略模式

两个模式的类图很相似,但是两个模式的行为不一样:策略模式替换算法是由用户自己控制的,状态模式替换算法是系统内部自行控制的。

总结

状态模式针对状态机场景有很好的模型总结,我们遇到状态机模型,都要首先考虑状态模式。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值