• 设计模式之状态模式(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());
    	} 
    }
    

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

    四.状态模式与策略模式

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

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

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

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

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

    • 状态模式实现了算法流程可变(即状态切换,不同的状态有不同的流程)
    • 策略模式实现了算法细节可选(即选择算法族内的算法,一个算法族包含多个可选算法)
  • 相关阅读:
    JSP内置对象和属性
    AWT和Swing
    Servlet的生命周期
    Servlet 与 CGI 的比较
    spring PROPAGATION
    JVM内存
    CISC, RISC 探究
    3.5.10 判断数字的奇偶
    java 中利用异或实现两个变量互换
    Java三大主流框架概述
  • 原文地址:https://www.cnblogs.com/ayqy/p/4004094.html
Copyright © 2020-2023  润新知