定义
状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
与策略模式是双胞胎。
它主要解决的问题就是当控制一个对象状态转换的条件表达式过于复杂时的情况。即把状态的判断逻辑转移到标识不同状态的一系列类当中。
适用场景
- 行为随状态改变而改变的场景。例如权限设计,人员对象权限不同执行相同的行为其结果也会不同。
- 代替者条件、分支语句,通过扩展子类实现条件判断处理。。
主要角色
- 环境(Context):含有状态的那个对象,它可以处理一些请求,这些请求最终产生的响应会与状态相关。
- 状态(State):它定义了每一个状态的行为集合,这些行为会在Context中得以使用。
- 具体状态(ConcreteState):实现相关行为的具体状态类
State可以使用接口或者抽象类,使用抽象类可以更方便的扩展新方法。
举例
源自《Head FIRST设计模式》糖果售卖机器。
实现一个糖果机,放入两个硬币可以吐出一个糖,期间可以退币。
因此这个糖果机有以下几种状态:没有硬币,有硬币,售出糖果,糖果售罄。如果采用逻辑控制语句实现这个功能,需要有大量的if-else来控制,而且很难扩展,未来新增一个新状态需要更改所有的控制语句。
首先让创建一个State接口
interface State{
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
然后将设计中的每个状态都封装成一个类,每个都实现state接口,拿NoQuarterState举例:
class NoQuarterState implements State{
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine){
this.gumballMachine=gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("如果有人投入了25分钱,我们就打印出一条消息,说我们接受了25分钱," +
"然后改变机器的状态到HasQuarterState");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("如果没给钱,就不要求退钱");
}
@Override
public void turnCrank() {
System.out.println("如果没给钱,就不能要求糖果");
}
@Override
public void dispense() {
System.out.println("如果没得到钱,我们就不能发放糖果。");
}
}
实现context类,
class GumballMachine {
//定义状态
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
//当前状态
State state = soldState;
//糖果余量
int count = 0;
public GumballMachine_(int count) {
//将自己
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldOutState = new SoldState(this);
this.count = count;
if (count > 0) {
state = noQuarterState;
}
}
//投入硬币
public void insertQuarter() {
state.insertQuarter();
}
//退币
public void ejectQuarter() {
state.ejectQuarter();
}
//转动把手
public void turnCrank(){
state.turnCrank();
state.dispense();
}
//设置当前state
void setState(State state){
this.state=state;
}
//释放糖果
void releaseBall(){
System.out.println();
if(count!=0){
count=count-1;
}
}
public State getSoldState() {
return soldState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getSoldOutState() {
return soldOutState;
}
}
同理,继续实现其他状态类
如果接下来要新增一个状态:十次抽中一次。只需要做如下改变:
首先要在GumballMahine类中加入一个状态
class GumballMachine {
//新增定义状态
State winnerState
实现这个winnerState:
class WinnerState implements State{
GumballMachine_ gumballMachine;
public WinnerState(GumballMachine_ gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("不恰当动作");
}
@Override
public void ejectQuarter() {
System.out.println("不恰当动作");
}
@Override
public void turnCrank() {
System.out.println("不恰当动作");
}
@Override
public void dispense() {
//如果还有第二个糖果我们就把它释放出来。
gumballMachine.releaseBall();
if(gumballMachine.getCount()==0){
gumballMachine.setState(gumballMachine.getSoldState());
}else{
gumballMachine.releaseBall();
if(gumballMachine.getCount()>0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
}else{
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
}
接下来只需要在HasQuarterState中,新增进入winnnerState的入口。
class HasQuarterState implements State {
//产生随机数
Random random = new Random(System.currentTimeMillis());
GumballMachine_ gumballMachine;
public HasQuarterState(GumballMachine_ gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("这是一个对此状态不恰当的动作");
}
@Override
public void ejectQuarter() {
System.out.println("退出顾客的25分钱,并将状态转换到NoQuarterState状态");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("当曲柄转动,我们将状态转换到SoldState");
int winner = random.nextInt(10);
//十分之一的概率进入winner模式
if ((winner == 0) && (gumballMachine.getCount() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState());
} else {
gumballMachine.setState(gumballMachine.getSoldState());
}
}
@Override
public void dispense() {
System.out.println("这是次状态的另一个不恰当动作");
}
}
注意
上面这种代码的环境类(糖果机)只能有一个实例,因为状态对象持有了环境类。如果想共享状态对象,可以把状态对象设置为静态,在handler()中传入Context的引用。例如参考的第三个博客:(二十一)状态模式详解(DOTA版)中的例子。
和策略模式关系
策略模式
可以发现和策略模式的类图是一模一样,但是这两个模式的差别在于它们的“意图”:策略模式通常会用行为或算法来配置Context类(客户对Contex的改变无需了解);状态模式允许Context随着状态的改变而改变行为。
优缺点
优点
- 结构清晰,避免了程序的复杂,提高了系统可维护性
- 体现了开闭原则和单一职责原则,每个状态都是一个子类,与单一职责原则高度符合,扩展状态只需增加子类,正是开闭原则的体现。封装性与迪米特法则也非常吻合
- 客户不会直接和状态交互,客户只需要投入硬币,转动把手等动作,不需要对这个机器与其他的了解
缺点
如果一个事务有很多个状态,就会造成子类太多的问题。需要在项目使用时来衡量是否使用状态模式