前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式之 —— 状态模式 State

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

作者头像
wsuo
发布2020-12-29 10:33:17
4130
发布2020-12-29 10:33:17
举报
文章被收录于专栏:技术进阶之路技术进阶之路

引例

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

对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 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 个。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-12-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引例
  • 定义
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档