设计模式之 —— 状态模式 State

2020-12-29 10:33:17 浏览数 (1)

引例

在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为 有状态的对象 ,而把影响对象行为的一个或多个动态变化的属性称为状态。

对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-elseswitch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。

状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关 “判断逻辑” 提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的 状态类对象 进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-elseswitch-case 等冗余语句。


我们现在来看一个例子:

现在城市发展很快,有两个东西在城市的发展过程中起到重要的作用,一个是汽车,另一个是电梯,因为汽车可以帮助城市横向扩展,电梯可以帮助城市纵向扩展。

今天我们就来聊聊电梯。

我们先来看看电梯都有哪几个状态:

  • 敞门状态:按了电梯按钮就是敞门了,这个状态下只能关门;
  • 闭门状态:电梯门关闭,这个状态下可以进行的动作是开门或者停止或者运行;
  • 运行状态:电梯正在跑,在这个状态下电梯只能停止;
  • 停止状态:电梯停止不动,这个状态下可以开门或者继续运行。

对应的状态机图画一下:

总共有四种状态,如果用 if-else 就要有 4 个分支,所以我们使用子类的方式,不使用分支结构。

创建四个子类,继承自一个状态的抽象类,该抽象类与环境类互相关联。

类图如下:

源码如下:

代码语言:javascript复制
package com.lsu.state.practice;

/**
 * 抽象电梯状态
 *
 * @Author wang suo
 * @Date 2020/12/26 0026 13:56
 * @Version 1.0
 */
public abstract class LiftState {

    /**
     * 定义一个环境角色,也就是封装状态的变换引起的功能变化
     */
    protected Context context;

    public void setContext(Context context) {
        this.context = context;
    }

    /**
     * 首先电梯门开启动作
     */
    public abstract void open();

    /**
     * 电梯门有开启,那当然也就有关闭了
     */
    public abstract void close();

    /**
     * 电梯要能上能下,跑起来
     */
    public abstract void run();

    /**
     * 电梯还要能停下来,停不下来那就扯淡了
     */
    public abstract void stop();

}
---
package com.lsu.state.practice;

/**
 * 敞门状态
 *
 * @Author wang suo
 * @Date 2020/12/26 0026 13:58
 * @Version 1.0
 */
public class OpenningState extends LiftState {

    /**
     * 开启当然可以关闭了,我就想测试一下电梯门开关功能
     */
    @Override
    public void close() {
        //状态修改
        super.context.setLiftState(Context.CLOSING_STATE);
        //动作委托为CloseState来执行
        super.context.getLiftState().close();
    }

    /**
     * 打开电梯门
     */
    @Override
    public void open() {
        System.out.println("电梯门开启...");
    }

    /**
     * 门开着电梯就想跑,这电梯,吓死你!
     */
    @Override
    public void run() {
        //do nothing;
    }

    /**
     * 开门还不停止?
     */
    @Override
    public void stop() {
        //do nothing;
    }

}
---
package com.lsu.state.practice;

/**
 * 关闭状态
 *
 * @Author wang suo
 * @Date 2020/12/26 0026 13:59
 * @Version 1.0
 */
public class ClosingState extends LiftState {

    /**
     * 电梯门关闭,这是关闭状态要实现的动作
     */
    @Override
    public void close() {
        System.out.println("电梯门关闭...");

    }

    /**
     * 电梯门关了再打开,逗你玩呢,那这个允许呀
     */
    @Override
    public void open() {
        //置为门敞状态
        super.context.setLiftState(Context.OPENNING_STATE);
        super.context.getLiftState().open();
    }

    /**
     * 电梯门关了就跑,这是再正常不过了
     */
    @Override
    public void run() {
        //设置为运行状态;
        super.context.setLiftState(Context.RUNNING_STATE);
        super.context.getLiftState().run();
    }

    /**
     * 电梯门关着,我就不按楼层
     */
    @Override
    public void stop() {
        //设置为停止状态;
        super.context.setLiftState(Context.STOPPING_STATE);
        super.context.getLiftState().stop();
    }

}
---
package com.lsu.state.practice;

/**
 * 运行状态
 *
 * @Author wang suo
 * @Date 2020/12/26 0026 14:00
 * @Version 1.0
 */
public class RunningState extends LiftState {

    /**
     * 电梯门关闭?这是肯定了
     */
    @Override
    public void close() {
        //do nothing
    }

    /**
     * 运行的时候开电梯门?你疯了!电梯不会给你开的
     */
    @Override
    public void open() {
        //do nothing
    }

    /**
     * 这是在运行状态下要实现的方法
     */
    @Override
    public void run() {
        System.out.println("电梯上下跑...");
    }

    /**
     * 这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
     */
    @Override
    public void stop() {
        //环境设置为停止状态;
        super.context.setLiftState(Context.STOPPING_STATE);
        super.context.getLiftState().stop();
    }

}
---
package com.lsu.state.practice;

/**
 * 停止状态
 *
 * @Author wang suo
 * @Date 2020/12/26 0026 14:01
 * @Version 1.0
 */
public class StoppingState extends LiftState {

    /**
     * 停止状态关门?电梯门本来就是关着的!
     */
    @Override
    public void close() {
        //do nothing;
    }

    /**
     * 停止状态,开门,那是要的!
     */
    @Override
    public void open() {
        super.context.setLiftState(Context.OPENNING_STATE);
        super.context.getLiftState().open();
    }

    /**
     * 停止状态再跑起来,正常的很
     */
    @Override
    public void run() {
        super.context.setLiftState(Context.RUNNING_STATE);
        super.context.getLiftState().run();
    }

    /**
     * 停止状态是怎么发生的呢?当然是停止方法执行了
     */
    @Override
    public void stop() {
        System.out.println("电梯停止了...");
    }

}
---
package com.lsu.state.practice;

/**
 * 环境类
 *
 * @Author wang suo
 * @Date 2020/12/26 0026 13:57
 * @Version 1.0
 */
public class Context {
    /**
     * 定义出所有的电梯状态
     */
    public final static OpenningState OPENNING_STATE = new OpenningState();
    public final static ClosingState CLOSING_STATE = new ClosingState();
    public final static RunningState RUNNING_STATE = new RunningState();
    public final static StoppingState STOPPING_STATE = new StoppingState();

    /**
     * 定一个当前电梯状态
     */
    private LiftState liftState;

    public LiftState getLiftState() {
        return liftState;
    }

    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        //把当前的环境通知到各个实现类中
        this.liftState.setContext(this);
    }

    public void open() {
        this.liftState.open();
    }

    public void close() {
        this.liftState.close();
    }

    public void run() {
        this.liftState.run();
    }

    public void stop() {
        this.liftState.stop();
    }
}

这就是状态模式。

定义

状态模式的核心是封装。它把受环境改变的对象行为 包装在 不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。

状态模式包含以下主要角色。

  • 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

代码如下:

代码语言:javascript复制
public abstract class State {

	//定义一个环境角色,提供子类访问
	protected Context context;
	
	//设置环境角色
	public void setContext(Context _context){
		this.context = _context;
	}
	
	//行为1
	public abstract void handle1();
	
	//行为2
	public abstract void handle2();
}
---
public class ConcreteState1 extends State {

	@Override
	public void handle1() {
		//本状态下必须处理的逻辑
	}

	@Override
	public void handle2() {
		//设置当前状态为stat2
		super.context.setCurrentState(Context.STATE2);
		//过渡到state2状态,由Context实现
		super.context.handle2();
	}
}
---
public class ConcreteState2 extends State {

	@Override
	public void handle1() {		
		//设置当前状态为stat1
		super.context.setCurrentState(Context.STATE1);
		//过渡到state1状态,由Context实现
		super.context.handle1();
	}
	@Override
	public void handle2() {
		//本状态下必须处理的逻辑
	}
}
---
public class Context {
	//定义状态
	public final static State STATE1 = new ConcreteState1();
	public final static State STATE2 = new ConcreteState2();
	
	//当前状态
	private State CurrentState;
	
	//获得当前状态
	public State getCurrentState() {
		return CurrentState;
	}
	
	//设置当前状态
	public void setCurrentState(State currentState) {
		this.CurrentState = currentState;
		//切换状态
		this.CurrentState.setContext(this);
	}
	
	//行为委托
	public void handle1(){
		this.CurrentState.handle1();
	}
	
	public void handle2(){
		this.CurrentState.handle2();
	}
}

环境角色有两个不成文的约束:

  1. 把状态对象声明为 静态常量 ,有几个状态对象就声明几个静态常量;
  2. 环境角色具有状态抽象角色定义的所有行为。

状态模式的使用场景:

  1. 行为随状态改变而改变的场景;
  2. 条件、分支判断语句的替代者;

状态模式适用于当某个对象在他的状态发生改变时,它的行为也随着发生比较大的变化,在使用时对象的状态最好不要超过 5 个。

0 人点赞