2023-03-27
原文作者:ClarenceZero 原文地址:https://blog.csdn.net/ClarenceZero/article/details/106887075

状态模式

定义

有限状态机

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

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

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

状态模式

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

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

状态机实现方式

1. 分支逻辑法

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

2. 查表法

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

202303272245010801.png

3. 状态模式

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

类图

202303272245023772.png

实现

202303272245032403.png

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中。然而,当转换是更动态的时候,通常就会放在状态类中。缺点是: 状态类之间产生了耦合。
阅读全文