实现状态模式

3
我正在Java中为我的应用程序实现状态模式,并需要一些澄清。
状态机有5个状态:State 1到State 5。 共有5个事件(Event1到Event5)会导致状态转移。 并非所有事件都适用于所有状态。如果在特定状态下不适用该事件,则应用程序将抛出异常。
当状态机初始化后,它从state1开始。
以下是接口和上下文类。
/*
 Interface defining the possible events in each state.
 Each Implementer will handle event in a different manner. 
*/
public interface State {
 /*
  Handlers for each event. Each Implementer will handle the vent in a different manner.
 */
 public void handleEvent1(StateContext context);
 public void handleEvent2(StateContext context);
 public void handleEvent3(StateContext context);
 public void handleEvent4(StateContext context);
 public void handleEvent5(StateContext context);
 // Method to enter state and do some action.
 public void enter(StateContext context);
 // Method to exit state and do some clean-up activity on exit .
 public void exit(StateContext context);
}

/*
  Context class which will handle the state change and delegate event to appropriate event handler of current state
*/
Class StateContext {

   private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

   private State currentState = null;

   StateContext() {
        currentState = new State1();
   }

   //Handle event1 and pass it to the appropriate event handler for the current state.
   public void handleEvent1() {
      currentState.handleEvent1(); 
   }
       .
       .
       .
   //Handle event5 and pass it to the appropriate event handler for the current state.
   public void handleEvent5() {
      currentState.handleEvent5(); 
   }

   // Method to change the state. 
   // This method will be called by each state when it needs to transit to a new state.
   public void changeState(State newState) {
          accquireLock();
          currentState.exit();
          currentState = newState;
          currentState.enter();           
   }

   // Release read lock and accquire write lock
   public void accquireLock() {
        lock.readLock().unlock()
        lock.writeLock().lock();
   }

   // accquire readlock and release write lock
   public void releaseLock() {
        lock.readLock().lock()
        lock.writeLock().unlock();
   }
}

为了简单起见,我只提供了一个状态的实现。
public class State1 implements State {
       public void handleEvent1(StateContext context) {
          //Hand1e Event 1
       }
              .
              .
              .
      public void handleEvent5(StateContext context) {
          //Handle Event 5
       }


       public void enter(StateContext context) {
           //Release the lock here
           context.releaseLock();
           /*Here is my question. Is it a  good java practice to expose accquire and release lock in Context object. And use the exposed method here to release lock. 
           */

           // Do some action on entering the state. This may take few seconds to finish

       }  
}

我希望只在进入状态后才释放锁。同时,我不想一直持有锁直到enter()函数结束,因为如果我在enter()函数完成之前一直持有锁,我就不能处理其他事件,而且可能会超时。对于一些不真正改变状态的事件,我们需要读取状态并根据状态进行处理或忽略它们。如果我不释放锁,它们就无法被处理。另外,在一些情况下,如果在进入状态时发生了关机事件(该事件会改变状态),则无法处理状态机。在这种情况下,我必须立即关闭状态机,因为在关机事件发生后继续执行enter()函数是不合适的。
我的问题是: 将accquireLock和releaseLock公开作为Context类的API并在每个状态类中使用它们是否是良好的Java编程实践?
谢谢, Arun

为什么在您的changeState()方法执行完毕后不调用releaseLock()呢?既然您在该方法的开头获取了锁,那么在方法结束时释放锁似乎是一致的设计,不是吗? - Deactivator2
对于我的应用程序,如果我在 enter() 正在进行时收到关闭事件以关闭机器,我不应该继续进行 enter()。继续可能会导致不良影响或除了浪费资源直到 enter() 完成之外没有任何有益结果。在某些情况下,enter 可能需要超过 5 分钟并发送大量请求。如果我继续请求,请求可能无法到达接收方,或者接收方可能会丢弃我的请求,在这种情况下,我应立即停止并移动到初始状态(需要锁定)。 - Arun
是的,这似乎是一个不错的选择。我会尝试将其融入我的应用程序中。谢谢Deactivator2 :) - Arun
抱歉,我漏掉了一个要点。有些事件只需要读取状态并相应地处理或忽略它们。如果我使用中断机制,由于正在处理enter()的线程持有写锁,因此无法读取状态。我已经编辑了帖子。 - Arun
在你的StateContext类中创建一个方法,用于接收关闭事件。在基础的State类中创建另一个方法,该方法将从第一个方法中调用,并将每个State类中的interrupted布尔值设置为true。然后,在每个Stateenter()功能块代码之后,检查interrupted布尔值是否为true,如果是,则退出该方法。 - Deactivator2
显示剩余3条评论
1个回答

0

回答这个问题,如果锁被状态“管理器”类持有,那么该类应该控制锁访问,而不是任何实际的状态类。

关于您关于保持锁定直到enter完成的说法,这正是锁的作用:您不希望其他方法介入或中断。相反,您应该开发某种事件队列来捕获所有事件并等待分发它们,如果接收对象忙,则等待分发。要么,为您知道将要中断的任何事件制定特定的异常,以便在触发指定的事件时可以绕过锁定。

如果选择使用一种中断方法,您将不得不在每个State类的enter方法中实现多个检查,以检查是否已触发关闭事件。我看到的唯一另一种工作方式是使每个State扩展Thread,以便您可以随意中断/停止它,尽管在这种情况下听起来不像一个有效的选项。


我同意你的观点,即如果经理持有锁,则只有他应该释放锁。在Manager中公开获取和释放锁作为API,并在State处理程序中获取和释放它是否是一个好的做法。例如,在State1类中,事件处理程序将如下所示:public void handleEvent1() { context.accquireLock(); //Do something; context.releaseLock(); } - Arun
@Arun 是的,那是一个很好的设计模式。另一个方法是将锁定代码放在上下文的事件处理程序中而不是状态中,但功能是相同的。 - Deactivator2
1
好的,谢谢。那么我的设计是在进入enter()方法后生成一个新线程来执行每个状态输入后需要完成的操作。通过这样做,锁将不会一直被持有,直到操作完成,我可以处理只需读取当前状态的事件。在完成操作即新生成的线程结束时,获取锁并更改状态(如果需要)。 - Arun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接