当JAVA遇到状态机

当JAVA遇到状态机

曾经有这样一个脑筋急转弯:把一头大象放进冰箱需要几步?当然了,这是一个老梗了,可能连三岁小孩都能毫不犹豫地回答出来:3步;打开,塞进去,再关上。或许,作为一个老梗,它已经笑果不佳,但如果我们从新的角度去分析,也能发现新的价值。从把大象塞进冰箱这个过程思考,有三个非常明确的步骤:
1. 打开冰箱门
2.把大象塞进去
3.关上冰箱门
如果从算法的角度来看,这就是一个典型的算法,符合了算法的有穷性,确定性,至于可行性的话,至少理论上是可行的。

   再换种角度,如果从冰箱和大象所处的状态的分解这个问题,我们可以得到一种非常不同的解决问题的思路,先来进行状态的分析:

    1.冰箱门关着,大象在外面

    2.冰箱门打开,大象在外面

    3.冰箱门打开,大象在里面

    4.冰箱门关着,大象在里面

从上面的分析可以清楚的看到在不同的时刻冰箱和大象处于不同的状态(冰箱有门或开或闭,大象在里面或在外面的状态),由冰箱大象这两个对象组成的一个整体也因它的组成对象状态的不同而处于不同的状态,但是无论是从个体的状态看,还是从整体的状态看,在任一时刻都只能有一个状态,也就是说同一时刻有且只有一个确定的状态,而不能同时有多个状态。就好像,在某一时刻,大象只能在里面或者不在里面,而不能同时在里面又不在里面。另外一个值得注意的现象是,在不同的状态下我们能够进行的动作是不同的。当处于“冰箱门关着,大象不在里面”状态时,我门能够进行的动作是“打开门”;当处于“冰箱门打开,大象正在进入冰箱”状态时,我们能进行的动作是“把大象塞进冰箱”,不能进行的动作是“打开门”......可以看到整个过程是很典型的“状态驱动行为”,即有什么行为是由处于什么状态决定的,状态在整个过程中处于核心的地位——这便是有限状态机的思想。上面提及的大象冰箱的例子就是一个非常简单的有限状态机模型。

   下面我就用编程的方式来实现上面那个有限状态机模型,并且模仿“把大象塞进冰箱”这个过程。

   要实现有限状态机,首要的就是状态机图的设计。或许上面的例子非常简单,简单到不需要状态机图,但仍建议画出状态机图。


状态机图(没有UML工具,用office的流程图替代,将就着用吧)

   既然已经画出了状态机图,那么把它转换成JAVA代码就是比较简单的事了。下面结合具体代码来讲解一下,讲解遵循从抽象到具体的的顺序,这也是我当初写这个示例代码遵循的一个顺序:
   1.com.zyzz.fsm包,这个包下有整个程序的核心实现,包含了状态接口,状态接口的初始实现(适配器类),状态机的实现,以及状态机内部的具体状态子类的实现
   2.com.zyzz.ele包,这个包下有对状态机的简单测试代码

   好了,接下来就是各个类:

   (1).com.zyzz.fsm.IState

package com.zyzz.fsm;
/**
 * 状态接口,在此接口内定义了用于完成把“把大象塞进冰箱”任务的相关操作
 * @author zyzz1995
 * 
 */
public interface IState {
	/**
	 * 预定义值,这个常量用于标识每个具体子类,在具体的实现中应该被覆盖,赋予唯一的,不重复的值
	 * 只有在这种情况下该常量才有意义
	 */
	public final static int STATE_ID=-1;
		/**
		 * 打开冰箱
		 */
		void openFridge();
		/**
		 *  放进大象
		 */
		void putElephant();
		/**
		 * 关闭冰箱
		 */
		void closeFridge();
		/**
		 * 打印状态信息
		 */
		void printStateInfo();
}

    上面接口的代码注释解释的很清楚了,这里就不赘述了。需要的强调一点是在之后这个接口的子类中对STATE_ID这个常量有一个比较巧的运用。

     (2).com.zyzz.fsm.StateAdapter

package com.zyzz.fsm;
/**
 * 这是一个实现了IState接口的适配器类,它给出了状态接口的初始实现,但它并不是直接可用的
 *它只是为了避免各个具体的状态子类直接实现状态接口而产生的重复的,相同的代码
 *每个具体的状态子类都应该继承自此类,根据需要重写相关方法
 * @author zyzz1995
 */
public class StateAdapter implements IState {
   public final static String ERROR_INFO="状态下不允许";
	
   public Machine targetMachine;
   private  String oprTag="";
   public   String stateTag="";
   
	/*public StateAdapter(Machine mac){
		targetMachine=mac;
	}*/
	@Override
	public void openFridge() {
		oprTag="打开冰箱";
		targetMachine.printErrInfo(stateTag+ERROR_INFO+oprTag);
	}

	@Override
	public void putElephant() {
		oprTag="把大象塞进冰箱";
		targetMachine.printErrInfo(stateTag+ERROR_INFO+oprTag);
	}

	@Override
	public void closeFridge() {
		oprTag="关上冰箱";
		targetMachine.printErrInfo(stateTag+ERROR_INFO+oprTag);
	}

	@Override
	public void printStateInfo() {}		
}
如注释所言,这是一个适配器类,它实现类IState接口,提供了IState接口的默认实现,当然这些实现都是打印一些诸如“在XXX状态不允许XXX操作”的信息,如果仅仅把它作为“机器”类的状态实现的话是不行的,因为任一一个操作只是打印出错信息而已,这对于完成任务没有如何用处。所以,必须它派生出子类,在具体的状态子类中有选择地重写一些方法,使其对完成任务起到应有的作用。至于为什么要存在这个适配器类,是因为,不同的状态子类的差异仅仅在于个别方法的实现不同,大部分方法的实现是一样的,如果直接现实IState接口,那么意味着每一个状态子类都要实现IState接口中的每一个方法,同时意味着相同的代码会被重复很多次,而这并不是我们所希望的,所以StateAdapter这个适配器类的存在是基于实现的需要。
     (3).com.zyzz.fsm.Machine

package com.zyzz.fsm;

/**
 * "机器类",是状态存在的场所,它依靠内部的状态运转起来的(由状态驱动)
 * 
 * @author zyzz1995
 * 
 */
public class Machine {
	private IState presentState;// 当前状态
	private IState[] states;// 所有状态组成的状态组

	/**
	 * 构造器
	 */
	public Machine() {
		// 构建所有状态
		states = new StateAdapter[4];
		states[FridgeClosedElephantOutState.STATE_ID] = new FridgeClosedElephantOutState();
		states[FridgeOpenElephantOutState.STATE_ID] = new FridgeOpenElephantOutState();
		states[FridgeOpenElephantInState.STATE_ID] = new FridgeOpenElephantInState();
		states[FridgeClosedElephantInState.STATE_ID] = new FridgeClosedElephantInState();
		// 设定,初始状态
		this.setState(FridgeClosedElephantOutState.STATE_ID);

	}

	// 几个具体的状态子类,设计成内部类,理由是具体的状态只与具体的机器有关,这些状态这里对于外界是透明的
	// 因此,应该把它们封装在“机器”类的内部,对外界隐藏,外部世界无法直接改变“机器”内部的状态,确保了“机器”的安全性
	// 这些类只使用一次,本来应该用匿名内部类的,但匿名内部类没有类名,因此无法体现其所代表的状态,可读性教差
	class FridgeClosedElephantOutState extends StateAdapter {

		public static final int STATE_ID = 0x0;

		public FridgeClosedElephantOutState() {
			this.targetMachine = Machine.this;
			this.stateTag = "冰箱关闭,大象在外面";
		}

		@Override
		public void openFridge() {
			targetMachine.setState(FridgeOpenElephantOutState.STATE_ID);
		}

		public void printStateInfo() {
			System.out.println("现在:" + stateTag);
		}

	}

	class FridgeOpenElephantOutState extends StateAdapter {

		public static final int STATE_ID = 0x1;

		public FridgeOpenElephantOutState() {
			this.targetMachine = Machine.this;
			this.stateTag = "冰箱开启,大象在外面";
		}

		@Override
		public void putElephant() {
			targetMachine.setState(FridgeOpenElephantInState.STATE_ID);
		}

		public void printStateInfo() {
			System.out.println("现在:" + stateTag);
		}

	}

	class FridgeOpenElephantInState extends StateAdapter {

		public static final int STATE_ID = 0x2;

		public FridgeOpenElephantInState() {

			this.targetMachine = Machine.this;
			this.stateTag = "冰箱开启,大象在里面";
		}

		@Override
		public void closeFridge() {
			targetMachine.setState(FridgeClosedElephantInState.STATE_ID);
		}

		public void printStateInfo() {
			System.out.println("现在:" + stateTag);
		}

	}

	class FridgeClosedElephantInState extends StateAdapter {

		public static final int STATE_ID = 0x3;

		public FridgeClosedElephantInState() {

			this.targetMachine = Machine.this;
			this.stateTag = "冰箱关闭,大象在里面";

		}

		public void printStateInfo() {
			System.out.println("现在:" + stateTag);
		}
	}

	/**
	 * 工具方法:打印错误信息到控制台
	 */
	public final void printErrInfo(String errInfo) {
		System.out.println("Error:" + errInfo);
	}

	/**
	 * 状态切换方法:设置当前状态。只有本类可见。
	 */
	private final void setState(int stateID) {
		presentState = states[stateID];
		presentState.printStateInfo();
	}

	// 下面是面向外部开放的接口
	public void openTheFridge() {
		presentState.openFridge();
	}

	public void putTheElephantIn() {
		presentState.putElephant();
	}

	public void closeTheFridge() {
		presentState.closeFridge();
	}

}

     好了,上面是重头戏。Machine类,传说中的“机器”类。里面有几个继承了StateAdapter的状态子类,作为驱动这个机器类核心。其实这个类也比较简单,注释看看就能理解,不详述了。

     (4).com.zyzz.ele.ElephantVsFridge

package com.zyzz.ele;

import com.zyzz.fsm.Machine;

public class ElephantVsFridge {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//下面演示用java+有限状态机的方式,实现“把大象塞进冰箱”。
		//虽然用这个当做例子可能有些“蛋痛”,但也算说明有限状态机的一个比较简单的例子
		
		Machine eleMac = new Machine();//创建一个“机器”实例,此时机器内部处于“冰箱关上,大象在外面状态”
		eleMac.openTheFridge();//打开冰箱门,此时内部处于“冰箱门打开,大象在外面状态”
		eleMac.putTheElephantIn();//塞进大象,此时内部处于“冰箱门打开,大象在里面状态”
		eleMac.closeTheFridge();//关上冰箱门,此时内部处于“冰箱门关上,大象在里面状态”
		/*
		 *如果任一换动什么语句的顺序或者注释掉其中一些语句,机器都会发出警告“xx状态下不允许xx操作”
		*但是从Machine类内部的实现看,并没有相关的状态判断语句,这就是是状态机另一个好处了:把庞大的条件分支转移到
		*各状态子类当中去,从而表面类“面条式”的语句,降低了出错的风险和后期维护的成本。当然,这一点在本例中不是很明显,
		*但也能说明问题了。
		*/
	}
}
  上面是个对Machine的简单测试,看看注释吧!
   好了,这个例子就到这里了。这就是我对有限状态机的一些个人见解,也不知道对不对,目前也是自己摸索学习中,欢迎高人斧正提携,也欢迎共同探讨学习,愿与各位共同分享,共同进步。


欢迎共同探讨编程问题,QQ1323022581

本文文档及源代码下载:http://download.csdn.net/detail/zyzzate/7614205



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值