设计模式之状态模式(State Pattern)

一.什么是状态模式?

把所有动作都封装在状态对象中,状态持有者将行为委托给当前状态对象

也就是说,状态持有者(比如汽车,电视,ATM机都有多个状态)并不知道动作细节,状态持有者只关心自己当前所处的状态(持有的状态对象是哪个),再把一切事情都交给当前状态对象去打理就好了,甚至都不用去控制状态切换(当然,状态持有者有权利控制状态切换,也可以选择做甩手掌柜。。)

二.举个例子

假设我们要模拟一个ATM机,有以下需求:

  • 取款,验证卡密,吐出现钞,结束服务
  • 若卡密验证失败或者余额不足,则直接弹出卡片,结束本次服务
  • 机器内无存钞,显示No Cash,并向银行发送无钞信息
  • 机器故障,显示No Service,并向维修人员发送维修请求

那么用户取款的过程应该是这样的:

这是用户操作的流程,对于ATM机而言还需要注意下面几点:

  • 获得用户输入金额后,不仅要验证用户卡内余额,还要验证ATM机内余额
  • 每一次成功的取款服务结束后,都要检查ATM机内余额(若无钞则进行相应处理)
  • 任何环节出现无法处理的错误,都按照故障来处理

-------

想想上面的取款过程,我们还必须考虑各种非法操作,比如:

  • 不插卡直接输入密码
  • 机器故障时仍然进行操作
  • 机内无钞时要求取款

细分之后,我们会发现细节问题变得很麻烦,机器就摆在那里,所有的用户接口都开放着,用户完全有可能在任何时候使用任何接口

为了防止这些非法操作,我们不得不添加一系列的判断,比如:

  • 验证密码之前应该检查是否已经插卡,以及机器是否发生了故障
  • 插卡之前应该检查机器是否发生了故障
  • 取款操作之前应该检查机器是否故障,是否无钞,是否。。。

我们的代码中需要设置大量的成员变量,用来标识机器的状态,更可怕的是我们的逻辑块里面存在大量的if-else,而且每一个操作里面的if-else块都只有细微的差别,看起来像冗余,但又不好处理(即使我们把每一个判断都提出来作为独立的方法,可以缩减代码规模,但在处理过程上仍然是冗余。。)

更好的处理方法就是使用状态模式,能够完全消除状态判断部分的冗余,并提供清晰整洁的代码结构

-------

下面用状态模式来实现例子中的需求

(1)首先找出ATM提供的所有接口

  1. 插卡
  2. 提交密码
  3. 取款(假设取款按钮是物理键)
  4. 查询(假设同上)
  5. 取卡

(2)再找出ATM的所有状态以及各个状态对应的动作

  • 准备就绪(Ready),可用接口:全部
  • 无钞(NoCash),可用接口:1,2,4,5
  • 故障(NoService),可用接口:无

(3)编码实现

先定义State基类,类中封装了(1)列出的所有接口:

package StatePattern;

/**
 * 定义ATM机状态
 * @author ayqy
 */
public interface ATMState {
	/**
	 * 插卡
	 */
	public abstract void insertCard();

	/**
	 * 提交密码
	 */
	public abstract void submitPwd();

	/**
	 * 取款
	 */
	public abstract void getCash();

	/**
	 * 查询余额
	 */
	public abstract void queryBalance();

	/**
	 * 取卡
	 */
	public abstract void ejectCard();
}

再逐一实现三个状态

ReadyState:

package StatePattern;

/**
 * 实现ATM就绪状态
 * @author ayqy
 */
public class ReadyState implements ATMState{
	private ATM atm;//保留状态持有者的引用,以便对其进行操作

	public ReadyState(ATM atm){
		this.atm = atm;
	}

	@Override
	public void insertCard() {
		System.out.println("插卡完成");
	}

	@Override
	public void submitPwd() {
		System.out.println("密码提交完成");
		//验证密码并做相应处理
		if("123".equals(atm.getPwd())){
			System.out.println("密码验证通过");
		}
		else{
			System.out.println("密码验证失败");
			//弹出卡片
			ejectCard();
		}
	}

	@Override
	public void getCash() {
		if(atm.getTotalAmount() >= atm.getAmount() && atm.getBalance() >= atm.getAmount()){
			//更新账户余额
			atm.setBalance(atm.getBalance() - atm.getAmount());
			//更新机内现钞总数
			atm.setTotalAmount(atm.getTotalAmount() - atm.getAmount());
			System.out.println("吐出¥" + atm.getAmount());
			System.out.println("取款完成");
			//弹出卡片
			ejectCard();
			//检查机内余钞
			if(atm.getTotalAmount() == 0){//若无钞,切换到NoService状态
				atm.setCurrState(atm.getNoCashState());
				System.out.println("无钞信息已经发送至银行");
			}
		}
		else{
			System.out.println("取款失败,余额不足");
			//弹出卡片
			ejectCard();
		}
	}

	@Override
	public void queryBalance() {
		System.out.println("余额¥" + atm.getBalance());
		System.out.println("余额查询完成");
	}

	@Override
	public void ejectCard() {
		System.out.println("取卡完成");
	}
}

注意我们在状态类中进行状态切换的部分:

if(atm.getTotalAmount() == 0){//若无钞,切换到NoService状态
	atm.setCurrState(atm.getNoCashState());
}

我们并不是直接new具体状态对象,而是使用了ATM提供的set接口,这样做是为了尽量的解耦(兄弟对象彼此之间并不认识),获取更多的弹性

实现NoCashState:

package StatePattern;

/**
 * 实现ATM无钞状态
 * @author ayqy
 */
public class NoCashState implements ATMState{
	private ATM atm;//保留状态持有者的引用,以便对其进行操作

	public NoCashState(ATM atm){
		this.atm = atm;
	}

	@Override
	public void insertCard() {
		System.out.println("插卡完成");
	}

	@Override
	public void submitPwd() {
		System.out.println("密码提交完成");
		//验证密码并做相应处理
		if("123".equals(atm.getPwd())){
			System.out.println("密码验证通过");
		}
		else{
			System.out.println("密码验证失败");
			//弹出卡片
			ejectCard();
		}
	}

	@Override
	public void getCash() {
		System.out.println("取款失败,机内无钞");
	}

	@Override
	public void queryBalance() {
		System.out.println("余额¥" + atm.getBalance());
		System.out.println("余额查询完成");
	}

	@Override
	public void ejectCard() {
		System.out.println("取卡完成");
	}
}

实现NoServiceState:

package StatePattern;

/**
 * 实现ATM故障状态
 * @author ayqy
 */
public class NoServiceState implements ATMState{
	private ATM atm;//保留状态持有者的引用,以便对其进行操作

	public NoServiceState(ATM atm){
		this.atm = atm;
	}

	@Override
	public void insertCard() {
		System.out.println("插卡失败,机器发生了故障");
	}

	@Override
	public void submitPwd() {
		System.out.println("密码提交失败,机器发生了故障");
	}

	@Override
	public void getCash() {
		System.out.println("取款失败,机器发生了故障");
	}

	@Override
	public void queryBalance() {
		System.out.println("余额查询失败,机器发生了故障");
	}

	@Override
	public void ejectCard() {
		System.out.println("取卡失败,机器发生了故障");
	}
}

实现了具体的状态,就可以构造ATM类了,就像这样:

package StatePattern;

/**
 * 实现ATM机
 * @author ayqy
 */
public class ATM {
	/*所有状态*/
	private ATMState readyState;
	private ATMState noCashState;
	private ATMState noServiceState;

	private ATMState currState;//当前状态
	private int totalAmount;//机内现钞总数

	/*测试用的临时变量*/
	private String pwd;//密码
	private int balance;//余额
	private int amount;//取款金额

	public ATM(int totalAmount, int balance, int amount, String pwd) throws Exception{
		//初始化所有状态
		readyState = new ReadyState(this);
		noCashState = new NoCashState(this);
		noServiceState = new NoServiceState(this);

		if(totalAmount > 0){
			currState = readyState;
		}
		else if(totalAmount == 0){
			currState = noCashState;
		}
		else{
			throw new Exception();
		}

		//初始化测试数据
		this.totalAmount = totalAmount;
		this.balance = balance;
		this.amount = amount;
		this.pwd = pwd;
	}

	/*把具体行为委托给状态对象*/
	/**
	 * 插卡
	 */
	public void insertCard(){
		currState.insertCard();
	}

	/**
	 * 提交密码
	 */
	public void submitPwd(){
		currState.submitPwd();
	}

	/**
	 * 取款
	 */
	public void getCash(){
		currState.getCash();
	}

	/**
	 * 查询余额
	 */
	public void queryBalance(){
		currState.queryBalance();
	}

	/**
	 * 取卡
	 */
	public void ejectCard(){
		currState.ejectCard();
	}

	public String toString(){
		return "现钞总数¥" + totalAmount;
	}

	/*此处略去大量getter and setter*/
}

一切都做好了,迫不及待的测试一下吧

三.运行示例

首先实现一个Test类:

package StatePattern;

import java.util.Scanner;

/**
 * 实现测试类
 * @author ayqy
 */
public class Test {
	public static void main(String[] args) {
		/*测试数据*/
		/* 机内总数	 账户余额	 取款金额	密码
		 * 1000	500     200		123
		 * 1000	300     500		123
		 * 0	500		200		123
		 * */
		try {
			test(1000, 500, 200, "123");
			System.out.println("-------");
			test(1000, 300, 500, "123");
			System.out.println("-------");
			test(0, 500, 200, "123");
		} catch (Exception e) {
			System.out.println("机器故障,维修请求已经发送至维修方");
		}
	}

	private static void test(int totalAmount, int balance, int amount, String pwd)throws Exception{
		//创建ATM
		ATM atm;
		atm = new ATM(totalAmount, balance, amount, pwd);
		//输出初始状态
		System.out.println(atm.toString());
		atm.insertCard();
		atm.submitPwd();
		atm.getCash();
		//输出结束状态
		System.out.println(atm.toString());
	}
}

我们设计的三个用例(正常取款,取大于余额的款,机内无现钞)运行结果如下:

四.状态模式与策略模式

还记得策略模式吗?难道不觉得这两者很相像吗?

没错,这两种模式的类图是完全相同的,解释一下:

  • 状态主体(拥有者)持有状态对象,运行时可以通过动态指定状态对象来改变类的行为
  • 策略主体持有算法族对象,运行时可以通过动态选择算法族中的算法(策略)来改变类的行为

也就是说,状态模式与策略模式都支持运行时的多态,并且其实现方式都是组合 + 委托

但是这并不代表这两种模式是相同的,因为它们的目标不同:

  • 状态模式实现了算法流程可变(即状态切换,不同的状态有不同的流程)
  • 策略模式实现了算法细节可选(即选择算法族内的算法,一个算法族包含多个可选算法)
时间: 2024-08-26 17:25:53

设计模式之状态模式(State Pattern)的相关文章

[设计模式] 20 状态模式 State Pattern

在GOF的<设计模式:可复用面向对象软件的基础>一书中对状态模式是这样说的:允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类.状态模式的重点在于状态转换,很多时候,对于一个对象的状态,我们都是让这个对象包含一个状态的属性,这个状态属性记录着对象的具体状态,根据状态的不同使用分支结构来执行不同的功能,就像上面的代码那样处理:就像上面说的,类中存在大量的结构类似的分支语句,变得难以维护和理解.状态模式消除了分支语句,就像工厂模式消除了简单工厂模式的分支语句一样,将状态处理分散

设计模式 - 状态模式(state pattern) 未使用状态模式 详解

状态模式(state pattern) 未使用状态模式 详解 本文地址: http://blog.csdn.net/caroline_wendy 状态模式可以控制状态的转换, 未使用设计模式时, 程序会非常繁杂. 具体方法: 1. 状态转换类. /** * @time 2014年7月11日 */ package state; /** * @author C.L.Wang * */ public class GumballMachine { final static int SOLD_OUT =

设计模式 - 状态模式(state pattern) 详解

状态模式(state pattern) 详解 本文地址: http://blog.csdn.net/caroline_wendy 状态模式(state pattern): 允许对象在内部状态改变时改变它的行为, 对象看起来好像修改了它的类. 建立Context类, 包含多个具体状态(concrete state)类的组合, 根据状态的不同调用具体的方法, state.handle(), 包含set\get方法改变状态. 状态接口(state interface), 包含抽象方法handle(),

设计模式之状态模式(State)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以

设计模式(行为型)之状态模式(State Pattern)

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober] 阅读前一篇<设计模式(行为型)之模板方法模式(Template Method Pattern)>http://blog.csdn.net/yanbober/article/details/45501715 概述 状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题.当系统中

设计模式 ( 十七) 状态模式State(对象行为型)

1.概述 在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理.最直接的解决方案是将这些所有可能发生的情况全都考虑到.然后使用if... ellse语句来做状态判断来进行不同情况的处理.但是对复杂状态的判断就显得“力不从心了”.随着增加新的状态或者修改一个状体(if else(或switch case)语句的增多或者修改)可能会引起很大的修改,而程序的可读性,扩展性也会变得很弱.维护也会很麻烦.那么我就考虑只修改自身状态的模式. 例子1:按钮来控制一个电梯的状态,一个电梯开们,关门,停,

状态模式(State Pattern)

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类. 状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况.把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化. 上下文环境(Context):它定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的Concrete State对象来处理. 抽象状态(State):定义一个接口以封装使用上下文环境的的一个特定状态相关的行为. 具体状态(Concrete State

行为型设计模式之状态模式(State)

结构 意图 允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类. 适用性 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为. 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态.这个状态通常用一个或多个枚举常量表示.通常, 有多个操作包含这一相同的条件结构.S t a t e模式将每一个条件分支放入一个独立的类中.这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化. 1 using Syste

设计模式 笔记 状态模式 State

//---------------------------15/04/28---------------------------- //State  状态模式----对象行为型模式 /* 1:意图: 允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类. 2:别名: 状态对象(Objects for States) 3:动机: 4:使用性: 1>一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为. 2>一个操作中含有庞大的多分支条件语句,且这些分支依赖于该

【设计模式】—— 状态模式State

前言:[模式总览]——————————by xingoo 模式意图 允许一个对象在内部改变它的状态,并根据不同的状态有不同的操作行为. 例如,水在固体.液体.气体是三种状态,但是展现在我们面前的确实不同的感觉.通过改变水的状态,就可以更改它的展现方式. 应用场景 1 当一个对象的行为,取决于它的状态时 2 当类结构中存在大量的分支,并且每个分支内部的动作抽象相同,可以当做一种状态来执行时. 模式结构 Context 环境角色,里面包含状态对象 class Context{ private Sta