设计模式之状态模式

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】 

状态模式

定义

有限状态机

状态存储关于过去的信息,就是说:它反映从系统开始到现在时刻的输入变化。转移指示状态变更,并且用必须满足确使转移发生的条件来描述它。动作是在给定时刻要进行的活动的描述。有多种类型的动作:

  • 进入动作(entry action):在进入状态时进行
  • 退出动作(exit action):在退出状态时进行
  • 输入动作:依赖于当前状态和输入条件进行
  • 转移动作:在进行特定转移时进行

FSM(有限状态机)可以使用上面图1那样的状态图(或状态转移图)来表示。此外可以使用多种类型的状态转移表。下面展示最常见的表示:当前状态(B)和条件(Y)的组合指示出下一个状态(C)。完整的动作信息可以只使用脚注来增加。包括完整动作信息的FSM定义可以使用状态表

状态模式

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

每个状态的行为局部化到它自己的类中,将容易产生问题的if语句删除、以便日后的维护。让每一个状态" 对修改关闭" 让糖果机对外开放,因为可以加入新的状态类。创建一个新的代码基和类结构。

状态机实现方式

1. 分支逻辑法

简单的说,就是在类中嵌套多重if/else判断语句。对于简单的状态机来说,分支实现是可以接受的。但是,对于复杂的状态机来说,这种实现方式极易漏写或者错写某个状态转换。而且可读性、可维护性都很差。

2. 查表法

实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示。相对于分支逻辑判断,查表法的代码实现更加清晰,可读性、可维护性更好。但只能支持简单的操作。如果执行一系列复杂的操作,我们就没有办法继续使用如此简单的二维数组来表示了。因此有一定局限性。

3. 状态模式

状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑。

类图

实现

1. 定义状态接口

    public interface State {
        /**
         * 投币动作
         */
        void insertQuarter();
    
        /**
         * 退回动作
         */
        void ejectQuarter();
    
        /**
         * 转动曲柄动作
         */
        void trunCrank();
    
        /**
         * 发放糖果
         */
        void dispense();
    }

2. 定义各种状态实现类

  1. 无币
        public class NoQuarterState implements State{
            GumballMachine gumballMachine;
        
            public NoQuarterState(GumballMachine gumballMachine) {
                this.gumballMachine = gumballMachine;
            }
        
            @Override
            public void insertQuarter() {
                System.out.println("塞入25分钱");
                gumballMachine.setState(gumballMachine.getHasQuarterState());
            }
        
            @Override
            public void ejectQuarter() {
                System.out.println("别这么厚脸皮,根本就没有投币");
            }
        
            @Override
            public void trunCrank() {
                System.out.println("别这么厚脸皮,根本就没有投币,也就不能获取糖果");
            }
        
            @Override
            public void dispense() {
                System.out.println("别这么厚脸皮,根本就没有投币");
            }
        
            @Override
            public String toString() {
                return "未投币";
            }
        }
  1. 有币
        public class HasQuarterState implements State{
            GumballMachine gumballMachine;
        
            public HasQuarterState(GumballMachine gumballMachine) {
                this.gumballMachine = gumballMachine;
            }
        
            @Override
            public void insertQuarter() {
                System.out.println("已经塞入25分钱,请勿重复塞入钱币");
            }
        
            @Override
            public void ejectQuarter() {
                System.out.println("钱币已退回");
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            }
        
            @Override
            public void trunCrank() {
                System.out.println("转动曲柄,即将发放糖果");
                gumballMachine.setState(gumballMachine.getSoldState());
            }
        
            @Override
            public void dispense() {
                System.out.println("请先转动曲柄,才能发放糖果");
            }
        
            @Override
            public String toString() {
                return "已投币";
            }
        }
  1. 出货中
        public class SoldState implements State {
            GumballMachine gumballMachine;
        
            public SoldState(GumballMachine gumballMachine) {
                this.gumballMachine = gumballMachine;
            }
        
            @Override
            public void insertQuarter() {
                System.out.println("糖果正在发放,请勿投币");
            }
        
            @Override
            public void ejectQuarter() {
                System.out.println("你都已经转动曲柄了,没有后悔药了");
            }
        
            @Override
            public void trunCrank() {
                System.out.println("不要重复转动曲柄");
            }
        
            @Override
            public void dispense() {
                gumballMachine.releaesBall();
                if (gumballMachine.getCount() > 0) {
                    gumballMachine.setState(gumballMachine.getSoldOutState());
                    System.out.println("本次糖果已发放");
                } else {
                    System.out.println("Ops, 本机器已没有糖果可给大家了");
                    gumballMachine.setState(gumballMachine.soldOutState);
                }
            }
        
            @Override
            public String toString() {
                return "发送中";
            }
        }
  1. 售完
        public class SoldOutState implements State{
            GumballMachine gumballMachine;
        
            public SoldOutState(GumballMachine gumballMachine) {
                this.gumballMachine = gumballMachine;
            }
        
            @Override
            public void insertQuarter() {
                System.out.println("投币成功");
                gumballMachine.setState(gumballMachine.getHasQuarterState());
            }
        
            @Override
            public void ejectQuarter() {
                System.out.println("当前无币,请投币");
            }
        
            @Override
            public void trunCrank() {
                System.out.println("当前无币,请投币");
            }
        
            @Override
            public void dispense() {
                System.out.println("当前无币,请投币");
            }
        
            @Override
            public String toString() {
                return "已发送";
            }
        }

2. 定义Context

    @Data
    public class GumballMachine {
        State soldOutState;
        State noQuarterState;
        State hasQuarterState;
        State soldState;
    
        State state;
        int count = 0;
    
        public GumballMachine(int count) {
            soldOutState = new SoldOutState(this);
            noQuarterState = new NoQuarterState(this);
            hasQuarterState = new HasQuarterState(this);
            soldState = new SoldState(this);
            state = noQuarterState;
            this.count = count;
        }
    
        public void insertQuarter() {
            state.insertQuarter();
        }
        public void ejectQuarter() {
            state.ejectQuarter();
        }
        public void turnCrank() {
            state.trunCrank();
            state.dispense();
        }
        public void releaesBall() {
            if (count != 0) {
                count -=1;
            }
        }
    
            @Override
        public String toString() {
            return  "当前糖果机器状态: " + state + ", 剩余糖果数: " + count;
        }
    }

3. Main方法

    public class M {
        public static void main(String[] args) {
            GumballMachine gumballMachine = new GumballMachine(2);
            System.out.println(gumballMachine);
    
            // 测试非法操作
            gumballMachine.turnCrank();
            gumballMachine.ejectQuarter();
    
            // 正常流程
            gumballMachine.insertQuarter();
            gumballMachine.turnCrank();
            System.out.println(gumballMachine);
        }
    }

总结

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

  2. 对于状态不多的、状态转移比较简单、但事件触发执行的动作包含的业务逻辑比较复杂的状态机来说,首选使用状态模式实现。

  3. 状态模式 VS 策略模式

    • 策略模式: 客户通常主动指定Context所要组合的对象是哪一个。一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很困难。有了策略模式,你可以组合不同的对象来改变行为。
    • 我们把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为。在context可以决定状态转换的流向。一般来说,当状态转换是固定的时候,就适合放在context中。然而,当转换是更动态的时候,通常就会放在状态类中。缺点是: 状态类之间产生了耦合。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值