《Head First 设计模式》笔记9

状态模式(State)

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

栗子

现在有一台糖果机,它的状态(挺复杂的):
- 没有 25 分钱 -> 投入 25 分钱 -> 有 25 分钱
- 有 25 分钱 -> 转动曲柄 -> 售出糖果(数量不为0) | 糖果售罄(数量为0)
- 有 25 分钱 -> 退钱按钮 -> 退出 25 分钱
- 售出糖果 -> 没有 25 分钱

从上面的状态实现代码的步骤:
1. 找出所有状态 -> 共四种:没有 25 分钱、有 25 分钱、售出糖果、糖果售罄。
2. 创建一个持有当前状态的实例变量 state。
3. 写出所有可能发生的动作判断。

class GumballMachine {
    private final static int SOLD_OUT = 0;
    private final static int NO_QUARTER = 1;
    private final static int HAS_QUARTER = 2;
    private final static int SOLD = 3;

    private int state = SOLD_OUT; // 当前状态
    private int count = 0; // 糖果数目

    public GumballMachine(int count) {
        this.count = count;
        // 初始糖果机的糖果数
        if (count > 0) {
            // 大于0表示等待别人投币
            state = NO_QUARTER;
        }
    }

    // 投进25分钱
    public void insertQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println("你已经投过25分钱了,请不要重复投币");
        } else if (state == NO_QUARTER) {
            System.out.println("投进25分钱");
            state = HAS_QUARTER;
        } else if (state == SOLD_OUT) {
            System.out.println("没有糖果了,不要投币");
        } else if (state == SOLD) {
            System.out.println("请稍等,正在出糖果");
        }
    }

    // 客户尝试退钱
    public void ejectQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println("已退还25分钱");
            state = NO_QUARTER;
        } else if (state == NO_QUARTER) {
            System.out.println("你还没投币呢");
        } else if (state == SOLD_OUT) {
            System.out.println("没有糖果时无法投币,不要骗钱");
        } else if (state == SOLD) {
            System.out.println("你已经转动曲柄了,无法退钱");
        }
    }

    // 客户转动曲柄
    public void turnCrank() {
        if (state == HAS_QUARTER) {
            System.out.println("你转动了曲柄");
            state = SOLD;
            dispense();
        } else if (state == NO_QUARTER) {
            System.out.println("你还没投币呢");
        } else if (state == SOLD_OUT) {
            System.out.println("已经没有糖果了");
        } else if (state == SOLD) {
            System.out.println("转几次都只能拿一次");
        }
    }

    // 糖果机发糖果
    public void dispense() {
        if (state == SOLD) {
            System.out.println("糖果已发出");
            count--;
            if (count == 0) {
                System.out.println("已经没有糖果喽");
                state = SOLD_OUT;
            } else {
                state = NO_QUARTER;
            }
        } else if (state == NO_QUARTER) {
            System.out.println("你还没投币呢");
        } else if (state == SOLD_OUT) {
            System.out.println("没有糖果发");
        } else if (state == SOLD) {
            System.out.println("没有糖果发");
        }
    }
}

多么缜密的判断,基本上没什么漏洞了。

新需求

现在产品经理提需求来了:当曲柄被转动时,有 10% 的几率掉出两个糖果。

看回上面缜密的代码,是不是有点无从入手?这个需求真是要了命了……如果需求再有变更,上面的代码基本上就得推倒重来了,因为代码逻辑判断太复杂。

满足需求

我们可以把这四个状态当作一个实体,比如糖果机当前处在没投币的状态,那么在该状态下,可以通过投币动作使糖果机转移到投了币状态,其他状态也是这样,基本上可以把 if else 这些判断语句分离出来。

定义状态接口:

interface State {
    void insertQuarter();

    void ejectQuarter();

    void turnCrank();

    void dispense();
}

然后每种状态都实现该接口(先看总体):

class SoldState implements State

class SoldOutState implements State

class NoQuarterState implements State

class HasQuarterState implements State

// 新需求中的“中奖状态”
class WinnerState implements State

还有新的糖果机(还没添加中奖状态):

class GumballMachine {
    private State soldState;
    private State soldOutState;
    private State noQuarterState;
    private State hasQuarterState;

    private State state = soldState;
    private int count = 0;

    public GumballMachine(int count) {
        soldState = new SoldState(this);
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        this.count = count;
        if (count > 0) {
            state = noQuarterState;
        }
    }

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

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

    // 转动曲柄后
    // 具体是否能发出糖果就需要当前状态是 hasQuarterState
    public void turnCrank() {
        state.turnCrank();
        state.dispense();
    }

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

    // 不再通过 dispense 发放糖果
    public void releaseBall() {
        System.out.println("糖果已发出");
        if (count != 0) {
            count--;
        }
    }

    // getter
    public State getState() {
        return state;
    }

    public int getCount() {
        return count;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }
}

分别实现的状态类

没投币的状态:

class NoQuarterState implements State {
    private 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 turnCrank() {
        System.out.println("你转动了曲柄,但你还没投币呢");
    }

    @Override
    public void dispense() {
        System.out.println("你要先投币");
    }
}

投了币的状态:

class HasQuarterState implements State {
    private GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("你已经投过25分钱了,请不要重复投币");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("已退还25分钱");
        gumballMachine.setState(gumballMachine.getNoQuarterState());
    }

    @Override
    public void turnCrank() {
        System.out.println("你转动了曲柄");
        gumballMachine.setState(gumballMachine.getSoldState());
    }

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

售出糖果的状态:

class SoldState implements State {
    private 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 turnCrank() {
        System.out.println("转几次都只能拿一次");
    }

    @Override
    public void dispense() {
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() > 0) {
            gumballMachine.setState(gumballMachine.getNoQuarterState());
        } else {
            System.out.println("已经没有糖果喽");
            gumballMachine.setState(gumballMachine.getSoldOutState());
        }
    }
}

糖果售罄的状态:

class SoldOutState implements State {
    private GumballMachine gumballMachine;

    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("没有糖果了,不要投币");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("没有糖果时无法投币,不要骗钱");
    }

    @Override
    public void turnCrank() {
        System.out.println("你转动了曲柄,但是已经没有糖果了");
    }

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

新需求中的中奖状态:

class WinnerState implements State {
    private GumballMachine gumballMachine;

    public WinnerState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("请稍等,正在出糖果");
    }

    @Override
    public void ejectQuarter() {
        System.out.println("你已经转动曲柄了,无法退钱");
    }

    @Override
    public void turnCrank() {
        System.out.println("转几次都只能拿一次");
    }

    // 除了发糖外,其他信息和售出糖果的状态一样
    @Override
    public void dispense() {
        System.out.println("恭喜!你拿到了两颗糖");
        // 第一颗糖
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() == 0) {
            gumballMachine.setState(gumballMachine.getSoldOutState());
        } else {
            // 第二颗糖
            gumballMachine.releaseBall();
            if (gumballMachine.getCount() > 0) {
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            } else {
                System.out.println("已经没有糖果喽");
                gumballMachine.setState(gumballMachine.getSoldOutState());
            }
        }
    }
}

需求还没解决完

上面的代码只是解决了状态的问题,但还没有完全满足需求,10% 的随机中奖还没写。(其实也没什么难度了)

给糖果机加上中奖状态:

class GumballMachine {
    // ...
    private State winnerState;

    public GumballMachine(String location, int count) {
        winnerState = new WinnerState(this);
        // ...
    }

    public State getWinnerState() {
        return winnerState;
    }

    // ...
}

因为只有在投了币的情况下才能转动曲柄 -> 发糖 | 中奖,所以要在投了币的状态加入这 10% 随机:

class HasQuarterState implements State {
    // ...

    @Override
    public void turnCrank() {
        System.out.println("你转动了曲柄");
        Random randomWinner = new Random(System.currentTimeMillis());
        // 中奖
        // 10次里面有一次是0,即 1/10
        int winner = randomWinner.nextInt(10);
        if (winner == 0 && gumballMachine.getCount() > 1) {
            gumballMachine.setState(gumballMachine.getWinnerState());
        } else {
            gumballMachine.setState(gumballMachine.getSoldState());
        }
    }

    // ...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值