设计模式 状态模式 以自动售货机为例

状态模式给了我眼前一亮的感觉啊,值得学习~

先看定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。定义又开始模糊了,理一下,当对象的内部状态改变时,它的行为跟随状态的改变而改变了,看起来好像重新初始化了一个类似的。

下面使用个例子来说明状态模式的用法,现在有个自动售货机的代码需要我们来写,状态图如下:

分析一个这个状态图:

a、包含4个状态(我们使用4个int型常量来表示)

b、包含3个暴露在外的方法(投币、退币、转动手柄)

c、我们需要处理每个状态下,用户都可以触发这三个动作。

下面我们根据分析的结果,写出代码:

package com.zhy.pattern.status;

/**
 * 自动售货机
 * 
 * @author zhy
 * 
 */
public class VendingMachine
{

    /**
     * 已投币
     */
    private final static int HAS_MONEY = 0;
    /**
     * 未投币
     */
    private final static int NO_MONEY = 1;
    /**
     * 售出商品
     */
    private final static int SOLD = 2;
    /**
     * 商品售罄
     */
    private final static int SOLD_OUT = 3;

    private int currentStatus = NO_MONEY;
    /**
     * 商品数量
     */
    private int count = 0;

    public VendingMachine(int count)
    {
        this.count = count;
        if (count > 0)
        {
            currentStatus = NO_MONEY;
        }
    }

    /**
     * 投入硬币,任何状态用户都可能投币
     */
    public void insertMoney()
    {
        switch (currentStatus)
        {
        case NO_MONEY:
            currentStatus = HAS_MONEY;
            System.out.println("成功投入硬币");
            break;
        case HAS_MONEY:
            System.out.println("已经有硬币,无需投币");
            break;
        case SOLD:
            System.out.println("请稍等...");
            break;
        case SOLD_OUT:
            System.out.println("商品已经售罄,请勿投币");
            break;

        }
    }

    /**
     * 退币,任何状态用户都可能退币
     */
    public void backMoney()
    {
        switch (currentStatus)
        {
        case NO_MONEY:
            System.out.println("您未投入硬币");
            break;
        case HAS_MONEY:
            currentStatus = NO_MONEY;
            System.out.println("退币成功");
            break;
        case SOLD:
            System.out.println("您已经买了糖果...");
            break;
        case SOLD_OUT:
            System.out.println("您未投币...");
            break;
        }
    }

    /**
     * 转动手柄购买,任何状态用户都可能转动手柄
     */
    public void turnCrank()
    {
        switch (currentStatus)
        {
        case NO_MONEY:
            System.out.println("请先投入硬币");
            break;
        case HAS_MONEY:
            System.out.println("正在出商品....");
            currentStatus = SOLD;
            dispense();
            break;
        case SOLD:
            System.out.println("连续转动也没用...");
            break;
        case SOLD_OUT:
            System.out.println("商品已经售罄");
            break;

        }
    }

    /**
     * 发放商品
     */
    private void dispense()
    {

        switch (currentStatus)
        {
        case NO_MONEY:
        case HAS_MONEY:
        case SOLD_OUT:
            throw new IllegalStateException("非法的状态...");
        case SOLD:
            count--;
            System.out.println("发出商品...");
            if (count == 0)
            {
                System.out.println("商品售罄");
                currentStatus = SOLD_OUT;
            } else
            {
                currentStatus = NO_MONEY;
            }
            break;

        }

    }
}

针对用户的每个动作,我们考虑了在任何状态下发生,并做了一定处理。下面进行一些测试:

package com.zhy.pattern.status;

public class TestTra
{
    public static void main(String[] args)
    {
        VendingMachine machine = new VendingMachine(10);
        machine.insertMoney();
        machine.backMoney();

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

        machine.insertMoney();
        machine.turnCrank();
        
        System.out.println("----------压力测试-----");
        machine.insertMoney();
        machine.insertMoney();
        machine.turnCrank();
        machine.turnCrank();
        machine.backMoney();
        machine.turnCrank();

    }
}

输出结果:

成功投入硬币
退币成功
-----------
成功投入硬币
正在出商品....
发出商品...
----------压力测试-----
成功投入硬币
已经有硬币,无需投币
正在出商品....
发出商品...
请先投入硬币
您未投入硬币
请先投入硬币

感觉还是不错的,基本实现了功能,但是有些事情是不可避免的,那就是需求的变化,现在为了提升销量,当用户每次转动手柄买商品的时候,有10%的几率赠送一瓶。

现在的状态图发生了变化,当用户转动手柄时,可能会达到一个中奖的状态:图如下:

如果在我们刚写的代码上直接添加,则需要在每个动作的switch中添加判断条件,且非常容易出错。所以现在我们要考虑重新设计我们的代码,我们考虑把每个状态写状态类,负责实现在对应动作下的行为,然后自动售货机在不能的状态间切换:

下面开始重构,我们现在有5种状态,对应4个动作(投币、退币、转动手柄、发出商品),下面首先定义一个状态的超类型:

package com.zhy.pattern.status.b;

/**
 * 状态的接口
 * @author zhy
 *
 */
public interface State
{
    /**
     * 放钱
     */
    public void insertMoney();
    /**
     * 退钱
     */
    public void backMoney();
    /**
     * 转动曲柄
     */
    public void turnCrank();
    /**
     * 出商品
     */
    public void dispense();
}

然后分别是每个状态的实现:

package com.zhy.pattern.status.b;

/**
 * 没钱的状态
 * @author zhy
 *
 */
public class NoMoneyState implements State
{

    private VendingMachine machine;

    public NoMoneyState(VendingMachine machine)
    {
        this.machine = machine;
        
    }
    
    @Override
    public void insertMoney()
    {
        System.out.println("投币成功");
        machine.setState(machine.getHasMoneyState());
    }

    @Override
    public void backMoney()
    {
        System.out.println("您未投币,想退钱?...");
    }

    @Override
    public void turnCrank()
    {
        System.out.println("您未投币,想拿东西么?...");
    }

    @Override
    public void dispense()
    {
        throw new IllegalStateException("非法状态!");
    }

}
package com.zhy.pattern.status.b;

import java.util.Random;

/**
 * 已投入钱的状态
 * 
 * @author zhy
 * 
 */
public class HasMoneyState implements State
{

    private VendingMachine machine;
    private Random random = new Random();

    public HasMoneyState(VendingMachine machine)
    {
        this.machine = machine;
    }

    @Override
    public void insertMoney()
    {
        System.out.println("您已经投过币了,无需再投....");
    }

    @Override
    public void backMoney()
    {
        System.out.println("退币成功");

        machine.setState(machine.getNoMoneyState());
    }

    @Override
    public void turnCrank()
    {
        System.out.println("你转动了手柄");
        int winner = random.nextInt(10);
        if (winner == 0 && machine.getCount() > 1)
        {
            machine.setState(machine.getWinnerState());
        } else
        {
            machine.setState(machine.getSoldState());
        }
    }

    @Override
    public void dispense()
    {
        throw new IllegalStateException("非法状态!");
    }

}
package com.zhy.pattern.status.b;

/**
 * 售罄的状态
 * 
 * @author zhy
 * 
 */
public class SoldOutState implements State
{

    private VendingMachine machine;

    public SoldOutState(VendingMachine machine)
    {
        this.machine = machine;
    }

    @Override
    public void insertMoney()
    {
        System.out.println("投币失败,商品已售罄");
    }

    @Override
    public void backMoney()
    {
        System.out.println("您未投币,想退钱么?...");
    }

    @Override
    public void turnCrank()
    {
        System.out.println("商品售罄,转动手柄也木有用");
    }

    @Override
    public void dispense()
    {
        throw new IllegalStateException("非法状态!");
    }

}
package com.zhy.pattern.status.b;

/**
 * 准备出商品的状态,该状态下,不会有任何用户的操作
 * 
 * @author zhy
 * 
 */
public class SoldState implements State
{

    private VendingMachine machine;

    public SoldState(VendingMachine machine)
    {
        this.machine = machine;
    }

    @Override
    public void insertMoney()
    {
        System.out.println("正在出货,请勿投币");
    }

    @Override
    public void backMoney()
    {
        System.out.println("正在出货,没有可退的钱");
    }

    @Override
    public void turnCrank()
    {
        System.out.println("正在出货,请勿重复转动手柄");
    }

    @Override
    public void dispense()
    {
        machine.dispense();
        if (machine.getCount() > 0)
        {
            machine.setState(machine.getNoMoneyState());
        } else
        {
            System.out.println("商品已经售罄");
            machine.setState(machine.getSoldOutState());
        }
    }
}
package com.zhy.pattern.status.b;

/**
 * 中奖的状态,该状态下不会有任何用户的操作
 * 
 * @author zhy
 * 
 */
public class WinnerState implements State
{

    private VendingMachine machine;

    public WinnerState(VendingMachine machine)
    {
        this.machine = machine;
    }

    @Override
    public void insertMoney()
    {
        throw new IllegalStateException("非法状态");
    }

    @Override
    public void backMoney()
    {
        throw new IllegalStateException("非法状态");
    }

    @Override
    public void turnCrank()
    {
        throw new IllegalStateException("非法状态");
    }

    @Override
    public void dispense()
    {
        System.out.println("你中奖了,恭喜你,将得到2件商品");
        machine.dispense();

        if (machine.getCount() == 0)
        {
            System.out.println("商品已经售罄");
            machine.setState(machine.getSoldOutState());
        } else
        {
            machine.dispense();
            if (machine.getCount() > 0)
            {
                machine.setState(machine.getNoMoneyState());
            } else
            {
                System.out.println("商品已经售罄");
                machine.setState(machine.getSoldOutState());
            }
            
        }

    }

}

最后是自动售货机的代码:

package com.zhy.pattern.status.b;

/**
 * 自动售货机
 * 
 * @author zhy
 * 
 */
public class VendingMachine
{
    private State noMoneyState;
    private State hasMoneyState;
    private State soldState;
    private State soldOutState;
    private State winnerState ; 

    private int count = 0;
    private State currentState = noMoneyState;

    public VendingMachine(int count)
    {
        noMoneyState = new NoMoneyState(this);
        hasMoneyState = new HasMoneyState(this);
        soldState = new SoldState(this);
        soldOutState = new SoldOutState(this);
        winnerState = new WinnerState(this);

        if (count > 0)
        {
            this.count = count;
            currentState = noMoneyState;
        }
    }

    public void insertMoney()
    {
        currentState.insertMoney();
    }

    public void backMoney()
    {
        currentState.backMoney();
    }

    public void turnCrank()
    {
        currentState.turnCrank();
        if (currentState == soldState || currentState == winnerState)
            currentState.dispense();
    }

    public void dispense()
    {
        System.out.println("发出一件商品...");
        if (count != 0)
        {
            count -= 1;
        }
    }

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

    //getter setter omitted ...

}

可以看到,我们现在把每个状态对应于动作的行为局部化到了状态自己的类中实现,不仅增加了扩展性而且使代码的阅读性大幅度的提高。以后再添加状态,只需要针对新添加的状态的实现类,并在自动售货机中添加此状态即可。

下面进行一些测试:

package com.zhy.pattern.status.b;

public class Test
{
    public static void main(String[] args)
    {
        VendingMachine machine = new VendingMachine(10);
        machine.insertMoney();
        machine.backMoney();

        System.out.println("----我要中奖----");

        machine.insertMoney();
        machine.turnCrank();
        machine.insertMoney();
        machine.turnCrank();
        machine.insertMoney();
        machine.turnCrank();
        machine.insertMoney();
        machine.turnCrank();
        machine.insertMoney();
        machine.turnCrank();
        machine.insertMoney();
        machine.turnCrank();
        machine.insertMoney();
        machine.turnCrank();

        System.out.println("-------压力测试------");

        machine.insertMoney();
        machine.backMoney();
        machine.backMoney();
        machine.turnCrank();// 无效操作
        machine.turnCrank();// 无效操作
        machine.backMoney();

    }
}

输出结果:

投币成功
退币成功
----我要中奖----
投币成功
你转动了手柄
发出一件商品...
投币成功
你转动了手柄
发出一件商品...
投币成功
你转动了手柄
发出一件商品...
投币成功
你转动了手柄
发出一件商品...
投币成功
你转动了手柄
发出一件商品...
投币成功
你转动了手柄
发出一件商品...
投币成功
你转动了手柄
你中奖了,恭喜你,将得到2件商品
发出一件商品...
发出一件商品...
-------压力测试------
投币成功
退币成功
您未投币,想退钱?...
您未投币,想拿东西么?...
您未投币,想拿东西么?...
您未投币,想退钱?...

恭喜你,又学会了一个设计模式,状态模式。最后看下状态模式的类图:

原文地址:https://www.cnblogs.com/zhujiabin/p/5390994.html