心血来潮写一篇关于状态机的文章,在学习状态机模式的时候最被熟悉的例子应该是自动售货机,所以就用自动售货机来写一篇状态机的文章吧。
首先分析自动售货机的状态,初步设计状态有初始化、商品选择中、出货中,有投币、退钱、锁定商品、出货成功事件,根据这几个状态和事件,设计了如下初步状态机图。
状态机图如上,接下来进入编码阶段。首先要确定的就是某个状态能处理哪些事件,某个状态收到某个事件可以去到哪个状态。开始状态只可以进行投币操作去到商品选择状态。商品选择状态只能锁定商品去到出货中状态或者重新回到商品选择状态。出货中状态只能出货完成去到商品选择状态或者开始状态。
接下来我们要思考的是自动售货机肯定不止一种,不同的自动售货机也许有不同的状态和事件,那么我们写代码如果想要做到通用就要抽象一些逻辑出来,先上代码。
public abstract class AbstractSaleMapProvider<S extends SaleState,H extends SaleHandler,E extends SaleEvent> {
Map<Class<? extends S>,Class<? extends H>> stateHandlerMap=new HashMap<>();
Map<Class<? extends S>,Map<Class<? extends E>,Set<Class<? extends S>>>> stateTranslationMap=new HashMap<>();
protected HandlerHolder forState( Class<? extends S> state){
return new HandlerHolder(state);
}
public class HandlerHolder {
private Class<? extends S> state;
private Class<? extends H> handler;
HandlerHolder( Class<? extends S> state) {
this.state = state;
}
public HandlerHolder addHandler(Class<? extends H> handler){
this.handler=handler;
return this;
}
public StateTranslationHolder forEvent(Class<? extends E> event){
return new StateTranslationHolder(this,event);
}
private void apply(StateTranslationHolder stateTranslationMapHolder){
stateHandlerMap.put(state,handler);
Map<Class<? extends E>,Set<Class<? extends S>>> m=Objects.nonNull(stateTranslationMap.get(state))?stateTranslationMap.get(state):new HashMap<>();
m.put(stateTranslationMapHolder.event,stateTranslationMapHolder.states);
stateTranslationMap.put(state,m);
}
}
public class StateTranslationHolder {
private HandlerHolder mapHolder;
private Class<? extends E> event;
private Set<Class<? extends S>> states=new HashSet<>();
StateTranslationHolder(HandlerHolder mapHolder, Class<? extends E> event) {
this.mapHolder=mapHolder;
this.event=event;
}
public StateTranslationHolder acceptState(Class<? extends S> state){
states.add(state);
return this;
}
public HandlerHolder apply(){
mapHolder.apply(this);
return mapHolder;
}
}
}
其中stateHandlerMap存储的是状态和状态处理逻辑的对应关系,stateTranslationMap存储的是当前状态支持去到哪个状态。接下来写一个X类型的自动售货机的实现。
class XSaleStateTransformMapProProvider extends AbstractSaleMapProvider<XSaleState, AbstractXSaleHandler, XSaleEvent> {
static SaleStateTransformMapProProvider instance=new SaleStateTransformMapProProvider();
private XSaleStateTransformMapProProvider() {
forState(XReadyState.class).addHandler(XReadyHandler.class)
.forEvent(XAddMoneyEvent.class).acceptState(XSelectState.class).apply();
forState(XSelectState.class).addHandler(XSelectHandler.class)
.forEvent(XLockProductSuccessfulEvent.class).acceptState(XShipmentState.class).apply()
.forEvent(XNotEnoughMoneyEvent.class).acceptState(XSelectState.class).apply();
forState(XShipmentState.class).addHandler(XShipmentHandler.class)
.forEvent(XSuccessfulShoppingEvent.class).acceptState(XSelectState.class).acceptState(XReadyState.class).apply();
}
}
上述逻辑不一定是严谨的,主要是为了展示各种状态和事件的对应关系的例子。
接下来就是要定义事件处理的具体逻辑了,首先定义一个抽象处理器,具体代码如下。
public abstract class AbstractXSaleHandler implements SaleHandler<XSaleEvent> {
private Map<Class<? extends XSaleEvent>,EventHandle> handleMap;
private XSaleFSM xSaleFSM;
AbstractXSaleHandler() {
handleMap=new HashMap<>();
handleMap.put(XAddMoneyEvent.class,this::handleXAddMoneyEvent);
handleMap.put(XLockProductEvent.class,this::handleXLockProductEvent);
handleMap.put(XLockProductSuccessfulEvent.class,this::handleXLockProductSuccessfulEvent);
handleMap.put(XNotEnoughMoneyEvent.class,this::handleXNotEnoughMoneyEvent);
handleMap.put(XRefundEvent.class,this::handleXRefundEvent);
handleMap.put(XSuccessfulShoppingEvent.class,this::handleXSuccessfulShoppingEvent);
}
@Override
public void handleEvent(XSaleEvent event) {
EventHandle eventHandle=handleMap.get(event.getClass());
if(Objects.isNull(eventHandle)){
throw new RuntimeException("系统出现了一些问题");
}
eventHandle.handle(event);
}
public void enter(){
System.out.println("当前进入状态:"+getSaleFSM().getSaleContext().xSaleState.getClass().getName());
}
public void handleXAddMoneyEvent(XSaleEvent event){
throw new RuntimeException("系统出现了一些问题");
}
public void handleXLockProductEvent(XSaleEvent event){
throw new RuntimeException("系统出现了一些问题");
}
public void handleXLockProductSuccessfulEvent(XSaleEvent event){
throw new RuntimeException("系统出现了一些问题");
}
public void handleXNotEnoughMoneyEvent(XSaleEvent event){
throw new RuntimeException("系统出现了一些问题");
}
public void handleXRefundEvent(XSaleEvent event){
throw new RuntimeException("系统出现了一些问题");
}
public void handleXSuccessfulShoppingEvent(XSaleEvent event){
throw new RuntimeException("系统出现了一些问题");
}
XSaleFSM getSaleFSM() {
return xSaleFSM;
}
public void setSaleFSM(XSaleFSM xSaleFSM) {
this.xSaleFSM = xSaleFSM;
}
@Override
public void nextState(XSaleState xSaleState) {
getSaleFSM().nextState(xSaleState);
}
@FunctionalInterface
interface EventHandle{
void handle(XSaleEvent event);
}
}
很显然,这里定义了一些模板方法来给子类具体实现,来看一看其中一个子类的具体实现吧。
public class XReadyHandler extends AbstractXSaleHandler{
@Override
public void handleXAddMoneyEvent(XSaleEvent event) {
Long money=event.getData();
getSaleFSM().setMoney(money);
System.out.println("您当前的余额为"+getSaleFSM().getMoney()+"!");
nextState(new XSelectState());
}
}
代码逻辑很清晰,在就绪状态就只会等待客户的投币操作,投币操作进行一个充值,然后将状态扭转到选择商品状态。下面就来看看状态扭转的具体逻辑实现吧。
public class XSaleFSM extends AbstractSaleFSM<XSaleState, AbstractXSaleHandler, XSaleEvent> {
private XSaleContext saleContext;
private XSaleEvent currentEvent;
XSaleFSM(XSaleContext saleContext) {
super(XSaleStateTransformMapProProvider.instance);
this.saleContext=saleContext;
}
public void sendEventToLockedFSM(XSaleEvent event) {
try {
handleEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleEvent(XSaleEvent event) throws IllegalAccessException, InstantiationException {
currentEvent=event;
getHandlerByState(saleContext.xSaleState).handleEvent(event);
}
private AbstractXSaleHandler getHandlerByState(XSaleState xSaleState) throws IllegalAccessException, InstantiationException {
Class<? extends AbstractXSaleHandler> handlerClass=stateHandlerMap.get(xSaleState.getClass());
if(Objects.isNull(handlerClass)){
throw new RuntimeException("系统异常");
}
AbstractXSaleHandler handler=handlerClass.newInstance();
if(Objects.isNull(handler)){
throw new RuntimeException("系统异常");
}
handler.setSaleFSM(this);
return handler;
}
public XSaleContext getSaleContext() {
return saleContext;
}
@Override
public void nextState(XSaleState xSaleState){
Map<Class<? extends XSaleEvent>, Set<Class<? extends XSaleState>>> eventTranslation=
stateTranslationMap.get(saleContext.xSaleState.getClass());
if(Objects.isNull(eventTranslation)){
throw new RuntimeException("系统异常");
}
Set<Class<? extends XSaleState>> allowStates=eventTranslation.get(currentEvent.getClass());
if(!allowStates.contains(xSaleState.getClass())){
throw new RuntimeException("系统异常");
}
try {
saleContext.xSaleState=xSaleState;
} catch (Exception e) {
throw new RuntimeException("系统异常");
}
try {
getHandlerByState(xSaleState).enter();
} catch (Exception e) {
throw new RuntimeException("系统异常");
}
}
handler持有一个状态机实例,状态机实例的nextState方法负责实现状态的扭转。此外这里的sendEventToLockedFSM方法负责了加锁对事件进行处理。下面就让我们启动我们的自动售货机运行试试。下面是启动方法。
public class FsmStart {
public static void main(String[] args) throws IOException {
XSaleFSM xSaleFSM= XSaleFSMManager.createXReadyStateFSM();
BufferedInputStream inputStream=new BufferedInputStream(System.in);
InputStreamReader inputStreamReader=new InputStreamReader(inputStream);
BufferedReader bufferedReader=new BufferedReader(inputStreamReader);
while (System.currentTimeMillis()>0){
System.out.println("请选择(AddMoney/LockProduct/Refund):");
String c=bufferedReader.readLine();
Operating o=Operating.valueOf(c);
switch (o){
case AddMoney:
System.out.println("请投币:");
String m=bufferedReader.readLine();
XAddMoneyEvent xAddMoneyEvent=new XAddMoneyEvent();
xAddMoneyEvent.withData(Long.valueOf(m));
xSaleFSM.sendEventToLockedFSM(xAddMoneyEvent);
break;
case LockProduct:xSaleFSM.sendEventToLockedFSM(new XLockProductEvent());break;
case Refund:xSaleFSM.sendEventToLockedFSM(new XRefundEvent());break;
default:System.out.println("系统好像出现了一些问题");
}
}
}
enum Operating{
AddMoney,
LockProduct,
Refund
}
}
下面是一次购物记录。
请选择(AddMoney/LockProduct/Refund):
AddMoney
请投币:
500
您当前的余额为500!
当前进入状态:copyright.sherlock.daily.fsm.x.state.XSelectState
请选择(AddMoney/LockProduct/Refund):
LockProduct
正在出货请稍等片刻哦!
当前进入状态:copyright.sherlock.daily.fsm.x.state.XShipmentState
出货成功了哦,记得取走您的商品!
您的余额为450可以选一些其它物品哦!
当前进入状态:copyright.sherlock.daily.fsm.x.state.XSelectState
请选择(AddMoney/LockProduct/Refund):
Refund
退款成功,期待您下次再来!
当前进入状态:copyright.sherlock.daily.fsm.x.state.XReadyState
请选择(AddMoney/LockProduct/Refund):