GoF23:State-状态

1、case:糖果机

1.1、业务背景

设计一个糖果机,工作原理如下:

  • 圆圈:代表一个状态;
  • 箭头:代表一个动作,即状态的转换;

image-20220212165215139

整理

  • 状态

    image-20220212165433797

  • 动作

    image-20220212165622275

1.2、CandyMachine

  1. 成员变量
    • state:代表状态;
    • count:代表糖果数目;
    • 静态常量:代表状态值;
  2. 方法:代表动作。
public class CandyMachine {
    private final static int NO_COIN = 0;
    private final static int HAS_COIN = 1;
    private final static int SOLD_OUT = 2;
    private final static int SOLD = 3;

    private int state;
    private int count;

    public CandyMachine(int count) {
        this.count = count;

        if (count > 0) {
            state = NO_COIN;
        } else {
            state = SOLD_OUT;
        }
    }

    public void insertCoin() {
        switch (state) {
            case NO_COIN:
                state = HAS_COIN;
                System.out.println("Inserted successfully");
                break;
            case HAS_COIN:
                System.out.println("Already inserted");
                break;
            case SOLD_OUT:
                System.out.println("Sold out, can't insert");
                break;
            case SOLD:
                System.out.println("Waiting for a candy");
                break;
            default:
        }
    }

    public void ejectQuarter() {
        switch (state) {
            case NO_COIN:
                System.out.println("No coin");
                break;
            case HAS_COIN:
                state = NO_COIN;
                System.out.println("Coin returned");
                break;
            case SOLD_OUT:
                System.out.println("Sold out, no coin");
                break;
            case SOLD:
                System.out.println("Already turned the crank");
                break;
            default:
        }
    }
    // turnCrank()
    // dispense()
}

1.3、分析

缺点

  1. 代码重复
    • 每个方法,必须对状态的所有情况进行判断(多重 if-else 或 switch-case 语句);
    • 每个方法,对状态的情况有相同的处理;
  2. 开闭原则
    • 加入新的状态,需要修改所有方法代码;
    • 业务流程改变,需要修改现有代码逻辑;
  3. 面向过程:该设计实际是面向过程,而不是面向对象。

改进

  1. 封装变化
    • 定义状态类,将状态的行为封装在类中
    • 局部化每个状态的行为,修改一个状态不会影响其它状态;
  2. 组合 & 委托
    • 作为 状态 的客户,CandyMachine 在类中定义成员变量(即状态);
    • 将请求委托给状态的方法处理。

2、State

2.1、设计方案

  1. 定义 State 接口
    • 成员变量:状态所属的客户(即糖果机)
    • 方法:对应客户的动作。
  2. 实现 State 类,实现该状态下的行为。
  3. 客户(CandyMachine)将动作委托给状态对象。

image-20220212175136500

2.2、State

接口

  • 成员变量:状态所属的客户,构造器注入;
  • 方法默认实现:抛出 UnsupportedOperationException 异常。
public class State {
    protected CandyMachine machine;

    public State(CandyMachine machine) {
        this.machine = machine;
    }

    public void insertCoin() {
        throw new UnsupportedOperationException("Unsupported Operation in current state");
    }

    public void ejectCoin() {
        throw new UnsupportedOperationException("Unsupported Operation in current state");
    }

    public void turnCrank() {
        throw new UnsupportedOperationException("Unsupported Operation in current state");
    }

    public void dispense() {
        throw new UnsupportedOperationException("Unsupported Operation in current state");
    }
}

实现类

继承 State

  • 对本状态有意义的操作方法,则重写;
  • 无意义的操作方法,使用默认实现即可。

实现

  1. NoCoinState

    public class NoCoinState extends State {
        public NoCoinState(CandyMachine machine) {
            super(machine);
        }
    
        @Override
        public void insertCoin() {
            machine.setState(machine.getHasCoinState());
            System.out.println("Inserted successfully");
        }
    }
    
  2. HasCoinState

    public class HasCoinState extends State {
        public HasCoinState(CandyMachine machine) {
            super(machine);
        }
    
        @Override
        public void ejectCoin() {
            machine.setState(machine.getNoCoinState());
            System.out.println("Coin returned");
        }
    
        @Override
        public void turnCrank() {
            machine.setState(machine.getSoldState());
            System.out.println("Crank turned");
        }
    }
    
  3. SoldState

    public class SoldState extends State {
        public SoldState(CandyMachine machine) {
            super(machine);
        }
    
        @Override
        public void dispense() {
            if (machine.getCount() > 0) {
                machine.releaseCandy();
                machine.setState(machine.getNoCoinState());
            } else {
                machine.setState(machine.getSoldOutState());
                System.out.println("Sorry, out of candy");
            }
        }
    }
    
  4. SoldOutState

    public class SoldOutState extends State {
        public SoldOutState(CandyMachine machine) {
            super(machine);
        }
    }
    

2.3、客户:CandyMachine

image-20220212192242146

  • 状态变量:修改为 State 接口类型,构造器初始化;
  • 动作方法:委托给状态类;
  • PS
    • 糖果机的用户转动曲柄后,机器自动分发糖果;
    • 因此,不需要定义turnCrank() 和 dispense() 两个方法,而是在 turnCrank() 中委托状态类的两个方法。
public class CandyMachine {
    private final State noCoinState;
    private final State hasCoinState;
    private final State soldOutState;
    private final State soldState;

    private State state;
    private int count;

    public CandyMachine(int count) {
        this.count = count;
        noCoinState = new NoCoinState(this);
        hasCoinState = new HasCoinState(this);
        soldOutState = new SoldOutState(this);
        soldState = new SoldState(this);

        if (count > 0) {
            state = noCoinState;
        } else {
            state = soldOutState;
        }
    }

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

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

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

    public void releaseCandy() {
        if (count > 0) {
            System.out.println("A candy comes...");
            count--;
        }
    }
	// setter:状态
	// getter:状态值、count
}

2.4、测试

@Test
public void test() {
    CandyMachine machine = new CandyMachine(5);
    machine.insertCoin();
    machine.ejectCoin();
    System.out.println("=======");
    machine.insertCoin();
    machine.turnCrank();
}

image-20220212190236389

3、状态模式

3.1、定义

状态模式允许对象在内部状态改变时,改变其行为

  • 状态封装成独立的类,并将动作委托给当前状态对象;
  • 通过组合和委托机制,引用不同的状态对象;
  • 在状态改变时修改对象的行为,对客户而言就像改变了类。
  • OOP原则
    • 开闭
    • 单一职责:负责持有状态实例,提供必要方法。而状态的改变由状态类内部完成。

3.2、类图

  • Context:上下文类
    • 内部持有 状态实例
    • 当 Context类 的方法被请求时,委托给状态类处理;
  • State:状态
    • 处理来自 Context 的请求;
    • 每个 ConcreteState 都提供了本状态对请求的实现;
    • 当 Context 状态改变时,行为也随之改变。

image-20220212211136988

3.3、策略 vs 状态

策略模式 和 状态模式 的类图完全相同,但模式的意图不同。

策略模式 状态模式
思想 封装算法 封装状态行为
组合对象 客户在运行时动态设置策略对象(主动) 状态对象按预先的设定改变(预设)
使用场景 作为继承的弹性替代方案 避免在 context 中使用大量的条件判断

3.4、分析

  1. 状态转换
    • 当状态转换是固定时,适合放在 Context 中;
    • 当状态转换是动态时,适合放在 状态类 中。(会导致状态类之间产生依赖)
  2. 客户不会直接与状态交互
    • 客户指 Context 类的用户;
    • 状态被组合在 Context 类中,用来表示它的内部状态和行为;
    • 只有 Context 类会对 状态类 发出请求。
  3. 使用状态模式,导致类的数目增加,但提高了系统弹性
  4. 使用 interface 或 class,都可以达到目的。但是 class 可以对方法默认实现
  5. State对象 可以被多个 Context实例 共享
    • 前提:State对象 不能持有它们自己的内部状态;
    • 做法:将每个状态,指定到静态实例变量中。

4、小结

状态模式允许对象在内部状态改变时,改变其行为。在状态改变时修改对象的行为,对客户而言就像改变了类。

  1. 封装变化:将状态封装成独立的类。局部化每个状态的行为,修改一个状态不会影响其它状态。
  2. 委托:将 客户 的请求,委托给当前状态对象。
  3. OOP原则:开闭、单一职责。
  4. 模式对比
    • 策略:封装算法;
    • 模板方法:封装算法结构(子步骤);
    • 状态:封装状态行为。
  5. 状态转变:根据状态转变的动态性,可由 Context类 或 State类 控制。
  6. State对象 可以被多个 Context实例 共享
posted @ 2022-02-08 17:18  Jaywee  阅读(49)  评论(0编辑  收藏  举报

👇