Java 设计模式之状态模式

本文详细介绍了状态模式的概念、组成结构及应用场景,并通过一个糖果机的例子展示了如何利用状态模式进行设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、了解状态模式

1.1 什么是状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它自己的类。

状态模式将状态封装为独立的类,并将动作委托到代表当前状态的对象。

1.2 状态模式组成结构

  • 上下文 (Context):用户对象,拥有(聚合)一个 State 类型的成员,以标识对象的当前状态。
  • 抽象状态 (State):接口或基类,封装与 Context 的特定状态相关的行为。
  • 具体状态 (ConcreteState):接口实现类或子类,实现了一个与 Context 某个状态相关的行为。

1.3 状态模式 UML 图解
这里写图片描述

1.4 状态模式与策略模式

如果对策略模式也了解的话,你会发现状态模式的类图与策略模式的类图几乎是一样的。它们之间有什么关系吗?
PS:策略模式具体博文http://blog.youkuaiyun.com/codejas/article/details/79077186

虽然状态模式与策略模式的类图是一样的,但是它们两个的“意图”是明显不一样的。

对于状态模式而言,我们将一群行为封装在状态对象中,context 的行为随时可委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出 context 内部的状态,因此,context 的行为也会随着改变。但是 context 的客户对于状态对象的了解程度不多,甚至根本是浑然不知。

而对于策略模式而言,客户通常主动指定 context 所需要的策略对象是哪一个。策略模式虽然可以让我们的代码更有弹性,但是对于某个 context 对象来说,通常都只有一个最适当的策略对象。

1.5 状态模式应用场景

  • 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
  • 一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态。
二、状态模式具体应用

2.1 问题描述

糖果机:一种新型糖果机的实现控制流程如图所示,现在需要我们用 Java 语言来实现它。
这里写图片描述

2.2 问题分析

从上面这个图中我们可以找出所有的状态:没有一块钱、有一块钱、售出糖果盒糖果告罄。也就是上图中的圆圈部分。为了提高程序的扩展性和维护性,我们采用状态模式来设计代码。将四个状态映射为对应的四个类。

我们需要做的事情如下:

  1. 首先定义一个 State 接口。在这个接口内,糖果机的每个动作都有一个对应的方法。
  2. 然后为机器中的每个状态实现状态类。这些类将负责在对应的状态下为糖果机工作。
  3. 将动作委托到状态类。

2.3 状态模式设计类图
这里写图片描述

2.4 代码实现

上下文糖果机 CandyMachine 类

package com.jas.state;

/**
 * 糖果机类
 * @author Jas
 * @create 2018-01-31 10:35
 **/
public class CandyMachine {
    State noCoinState;
    State hasCoinState;
    State soldState;
    State soldOutState;

    State state = soldOutState;
    /** 刚开始糖果机中是没有糖果的,所以 count 为 0 */
    int count  = 0;    

    /**
     * 通过构造函数实例化所有状态对象
     * @param numberCandys 初始化糖果机中的糖果数量
     */
    public CandyMachine(int numberCandys){
        noCoinState = new NoCoinState(this);
        hasCoinState = new HasCoinState(this);
        soldState = new SoldState(this);
        soldOutState = new SoldOutState(this);
        this.count = numberCandys;

        //如果超过 0 颗糖果,就把状态设为 noCoinState
        if(numberCandys > 0){
            state = noCoinState;
        }
    }

    public void insertCoin(){
        state.insertCoin();
    }

    public void ejectCoin(){
        state.ejectCoin();
    }

    public void turnCrank(){
        state.turnCrank();
        state.dispense();
    }

    /**
     * 该方法用于释放糖果,并将 count 的数量减一
     */
    void releaseBall(){
        System.out.println("糖果机释放一个糖果 ...");
        if (count != 0){
            count --;
        }
    }

    public State getNoCoinState() {
        return noCoinState;
    }

    public State getHasCoinState() {
        return hasCoinState;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public int getCount() {
        return count;
    }

    public void setState(State state){
        this.state = state;
    }
}

抽象状态 State 接口

package com.jas.state;

/**
 * 所有的状态接口
 * @author Jas
 * @create 2018-01-31 10:28
 **/
public interface State {
    /** 投入一个硬币 */
    void insertCoin();
    /** 弹出硬币 */
    void ejectCoin();
    /** 转动曲柄 */
    void turnCrank();
    /** 糖果分发或告罄 */
    void dispense();
}

具体状态 NoCoinState 类

package com.jas.state;

/**
 * @author Jas
 * @create 2018-01-31 10:30
 **/
public class NoCoinState implements State {
    CandyMachine candyMachine;

    public  NoCoinState(CandyMachine candyMachine){
        this.candyMachine = candyMachine;
    }

    /**
     * 投入硬币后,将糖果机置为有一个硬币的状态
     */
    @Override
    public void insertCoin() {
        System.out.println("你投入了一个硬币!");
        candyMachine.setState(candyMachine.getHasCoinState());
    }

    @Override
    public void ejectCoin() {
        System.out.println("你还没有投入一个硬币!");
    }

    @Override
    public void turnCrank() {
        System.out.println("即使你转动了曲柄,但是不会出糖果,因为你没有投入硬币!");
    }

    @Override
    public void dispense() {
        System.out.println("你需要先投入一个硬币!");
    }
}

具体状态 HasCoinState 类

package com.jas.state;

/**
 * @author Jas
 * @create 2018-01-31 10:39
 **/
public class HasCoinState implements  State {
    CandyMachine candyMachine;

    public HasCoinState(CandyMachine candyMachine){
        this.candyMachine = candyMachine;
    }

    @Override
    public void insertCoin() {
        System.out.println("你已经投入了一个硬币,不能再投入硬币了!");
    }

    /**
     * 在糖果机有一个硬币的时候,你可以申请退回你的硬币,并改变糖果机的状态
     */
    @Override
    public void ejectCoin() {
        System.out.println("您的硬币已退回!");
        candyMachine.setState(candyMachine.getNoCoinState());
    }

    /**
     * 糖果机有硬币的时候,你可以转动曲柄,从而获得糖果,并改变糖果机的状态
     */
    @Override
    public void turnCrank() {
        System.out.println("你转动了曲柄,请稍等 ...");
        candyMachine.setState(candyMachine.getSoldState());
    }

    @Override
    public void dispense() {
        System.out.println("现在还没有糖果发放!");
    }
}

具体状态 SoldState 类

package com.jas.state;

/**
 * @author Jas
 * @create 2018-01-31 10:40
 **/
public class SoldState implements State {
    CandyMachine candyMachine;

    public SoldState(CandyMachine candyMachine){
        this.candyMachine = candyMachine;
    }

    @Override
    public void insertCoin() {
        System.out.println("现在不能投入硬币了,请稍等,马上为您发放一个糖果!");
    }

    @Override
    public void ejectCoin() {
        System.out.println("您已经转动了曲柄,现在不能再退回硬币了!");
    }

    @Override
    public void turnCrank() {
        System.out.println("您已经转动了曲柄一次,多余的转动是无效的!");
    }

    /**
     * 分发糖果,并根据糖果机中的糖果数量,改变糖果机的状态
     */
    @Override
    public void dispense() {
        candyMachine.releaseBall();

        //如果糖果机中有糖果,将糖果机置为初始没有硬币的状态
        if(candyMachine.getCount() > 0){
            candyMachine.setState(candyMachine.getNoCoinState());
        }else {
            System.out.println("糖果已经售卖完了,请改天再来!");
            candyMachine.setState(candyMachine.getSoldOutState());
        }
    }
}

具体状态 SoldOutState 类

package com.jas.state;

/**
 * @author Jas
 * @create 2018-01-31 10:41
 **/
public class SoldOutState implements State {
    CandyMachine candyMachine;

    public SoldOutState(CandyMachine candyMachine){
        this.candyMachine = candyMachine;
    }

    @Override
    public void insertCoin() {
        System.out.println("请不要再投入硬币了,糖果已经卖完了!");
    }

    @Override
    public void ejectCoin() {
        System.out.println("您没有投入硬币,所以现在不能弹出硬币!");
    }

    @Override
    public void turnCrank() {
        System.out.println("即使你转动了曲柄,但还是不会出糖果,因为已经没有糖果了!");
    }

    @Override
    public void dispense() {
        System.out.println("糖果已经卖完了,没有糖果用于发放了!");
    }
}

测试类

package com.jas.state;

/**
 * @author Jas
 * @create 2018-01-31 11:04
 **/
public class CansyMachineTestDrive {
    public static void main(String[] args) {

        //初始化糖果机中的糖果数量为 1 个
        CandyMachine candyMachine = new CandyMachine(1);    

        candyMachine.insertCoin();
        candyMachine.ejectCoin();

        System.out.println("======================");

        candyMachine.insertCoin();
        candyMachine.turnCrank();

        System.out.println("======================");

        candyMachine.insertCoin();
        candyMachine.turnCrank();
    }
}
     /**
     * 输出
     * 你投入了一个硬币!
     * 您的硬币已退回!
     * ======================
     * 你投入了一个硬币!
     * 你转动了曲柄,请稍等 ...
     * 糖果机释放一个糖果 ...
     * 糖果已经售卖完了,请改天再来!
     * ======================
     * 请不要再投入硬币了,糖果已经卖完了!
     * 即使你转动了曲柄,但还是不会出糖果,因为已经没有糖果了!
     * 糖果已经卖完了,没有糖果用于发放了!
     */

2.5 问题总结

在上面这个例子中,客户并不直接与具体状态类直接交互,而是通过糖果机类向状态类发出请求,还有要注意的是,客户并不会直接改变糖果机的状态。了解状态的是糖果机类,客户并不了解,所以客户不会直接与状态类交互。在糖果机类中有一个状态实例,用这个实例来完成状态之间不同的切换。

三、 状态模式总结

3.1 状态模式的优缺点

优点

  • 封装了转换规则。
  • 枚举可能的状态,在枚举状态之前需要确定状态种类。
  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点

  1. 状态模式的使用必然会增加系统类和对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  3. 状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

3.2 状态模式知识总结

  • 状态模式允许一个对象基于内部的状态而拥有不同的行为。
  • 状态模式用类来表示状态。
  • 上下文 (Context) 会将行为委托给当前的状态对象。
  • 状态模式和策略模式虽然有相同的类图,但是它们的意图是不一样的。
  • 状态模式允许上下文随着状态的改变而改变行为。
  • 使用状态模式通常会导致类的数量大量增加。
  • 状态类可以被多个上下文 (Context) 实例共享。

PS:点击了解更多设计模式 http://blog.youkuaiyun.com/codejas/article/details/79236013

参考文献

《Head First 设计模式》
http://www.runoob.com/design-pattern/state-pattern.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值