关闭

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

标签: 设计模式状态模式
16374人阅读 评论(26) 收藏 举报
分类:

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/26350617

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

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

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


分析一个这个状态图:

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件商品
发出一件商品...
发出一件商品...
-------压力测试------
投币成功
退币成功
您未投币,想退钱?...
您未投币,想拿东西么?...
您未投币,想拿东西么?...
您未投币,想退钱?...

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


最后,欢迎大家留言~






70
4
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

HeadFirst 设计模式 10状态模式(糖果机)

111
  • ysqjyjy
  • ysqjyjy
  • 2015-08-17 13:56
  • 243

【设计模式】学习笔记14:状态模式(State)

认识状态模式 假设有一个糖果机, 它的工作状态图如下:要用代码实现糖果机的功能, 如果不用状态模式: 一种方法是创建一个类,它的作用就是一个状态机,对每一个动作,我们都创建了一个对应的方法,这些方法...
  • shuangde800
  • shuangde800
  • 2013-08-22 00:15
  • 19488

设计模式-状态模式

如果要实现一个状态机,对象内的状态建模的通用技巧是 创建一个实例变量来持有状态值并在方法内书写条件代码来处理不同的状态。 设计一大概模版如下 public class TestState{ f...
  • u010786109
  • u010786109
  • 2015-06-08 14:18
  • 770

状态模式

定义     状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。     具体来说就是:因为这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为...
  • u012367513
  • u012367513
  • 2014-08-13 11:02
  • 841

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

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/26350617 状态模式给了我眼前一亮的感觉啊,值得学习~ 先看定义:...
  • qq_32523587
  • qq_32523587
  • 2017-05-30 22:09
  • 221

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

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/26350617 状态模式给了我眼前一亮的感觉啊,值得学习~ 先看定义:...
  • walid1992
  • walid1992
  • 2016-05-22 08:31
  • 2044

设计模式-状态模式

模式动机在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象...
  • u010321471
  • u010321471
  • 2016-08-17 21:14
  • 1497

设计模式-状态模式实现状态机

1.概述 在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理。最直接的解决方案是将这些所有可能发生的情况全都考虑到。然后使用if... ellse语句来做状态判断来进行不同情况的处理。...
  • wuqingyidongren
  • wuqingyidongren
  • 2016-05-31 13:34
  • 2667

设计模式之状态机模式

ZT From :http://www.jdon.com/designpatterns/designpattern_State.htm State模式的定义: 不同的状态,不同的行为;或者说,每个状...
  • mfc11
  • mfc11
  • 2013-11-24 17:47
  • 19967

Java设计模式之八--状态模式(以自动售货机为例)

前言:好想回家。 转载出处:http://blog.csdn.NET/lmj623565791/article/details/26350617 状态模式给了我眼前一亮的感觉啊,值得学习~ ...
  • wu371894545
  • wu371894545
  • 2016-12-26 20:39
  • 9693
    个人资料
    • 访问:17922312次
    • 积分:51373
    • 等级:
    • 排名:第66名
    • 原创:205篇
    • 转载:0篇
    • 译文:6篇
    • 评论:15630条
    我的微信公众号
    联系方式


    QQ群:
    • 55032675
    • 423372824
    • 497438697
    • 请勿重复加群,Thx
    博客专栏
    统计