Domanda

Apologies if this is answered elsewhere; couldn't find enough information to convince myself of the best way to do this. I also realize this is a lengthy explanation with no code, but let me know if I should whip up some sample code to help demonstrate what I'm doing.

Basically:

  • implementing a communication protocol in Java using System.in/out
  • current approach is implementing a state pattern where a Scanner is instantiated on System.in in the context class
  • concrete states invoke a context method to read from the Scanner, and subsequently perform actions/transition states appropriately based on the value returned by the Scanner

My original intent for using a state pattern was to simplify the code when parsing sequences like this from System.in (don't ask about the syntax, it's something I have to work with):

  • COMMAND NAME=X
  • HEADER
  • line of header information
  • CONTENTS
  • lines of command contents
  • ENDCONTENTS
  • ENDCOMMAND

I generally define a concrete state for each type of command I expect to receive. Using the above sequence as an example, I would have a state set that looked something like {WAITING_FOR_COMMAND, COMMAND_RECEIVED, PARSING_HEADER, PARSING_CONTENTS, PARSING_DONE, COMMAND_PROCESSED}. I'd initially be in WAITING_FOR_COMMAND, then when "COMMAND NAME=X" is received I'd transition to COMMAND_RECEIVED, then when "HEADER" came in I'd transition to PARSING_HEADER, etc. This design makes traversing all the edge cases in the protocol easier, and also makes the code easy to update/maintain for when the protocol gets tweaked. Obviously much better than massive switch statements and repetitive boundary checks.

The problem I'm having is that I find myself declaring more and more state variables in the context class as I flesh out my concrete state behaviors, and know this is probably bad since I'm creating very exposed interfaces and very high linkages between the context and concrete state classes. The command sequences in this protocol can be arbitrarily long, and I need to save information imparted by each item in the command sequence until the command sequence is complete.

Using the above command sequence as an example, after "COMMAND ID=X", I want to save the value X for future use after I receive "ENDCOMMAND" and fully process the command. After "HEADER", I want to save the header information for future use after I receive "ENDCOMMAND" for when I actually process the command. So on and so forth. Simply adding commandId and header state variables to the context class works for now, but doesn't seem clean or well encapsulated to me at all.

Does anyone have any high level suggestions on how they'd approach this problem? Is there a better use of a state design pattern for this?

Just to note some ideas I've been playing with:

  • defining state contexts for each type of command sequence, and invoking the appropriate context upon receiving the relevant command from System.in; this seems almost as messy as having giant switch blocks, and seems to overly convolute the design
  • designing a full-blown FSM architecture that supports composite FSMs, where each command sequence occupies its own FSM within an overarching FSM; this seems like overkill to me
  • creating a ProtocolCommand object with various subclasses for each command sequence type; I could pass these into each state when transitioning, and gradually build them up as I go...but this clutters the state interface and forces all states to ingest a parameter that they won't necessarily use

Thanks so much! Sorry this was so verbose, let me know if I can clarify anything.

È stato utile?

Soluzione

I have used an enum for this style of parsing with one enum for each state. An example is here http://vanillajava.blogspot.co.uk/2011/06/java-secret-using-enum-as-state-machine.html

interface Context {
    ByteBuffer buffer();
    State state();
    void state(State state);
}
interface State {
    /**
       * @return true to keep processing, false to read more data.
     */
    boolean process(Context context);
}
enum States implements State {
    XML {
        public boolean process(Context context) {
            if (context.buffer().remaining() < 16) return false;
            // read header
            if(headerComplete)
                context.state(States.ROOT);
            return true;
        }
    }, ROOT {
        public boolean process(Context context) {
            if (context.buffer().remaining() < 8) return false;
            // read root tag
            if(rootComplete)
                context.state(States.IN_ROOT);
            return true;
        }
    }
}

public void process(Context context) {
    socket.read(context.buffer());
    while(context.state().process(context));
}

Altri suggerimenti

I would first consider the following points :

  1. Are your state components (WAITING_FOR_COMMAND, COMMAND_RECEIVED etc )central to your application logic ? That is to say , does it really matter a lot what state the parser is in , and what state it transitions to , for the app to work correctly ? What are the values that each of the state is going to hold ? Do these values differ greatly from one state to another ? If your answers are yes , then probably you have a valid case for State pattern.But I have often seen state machines used in places where it was simply an overkill.

  2. Your use-case seems more like in need of Command pattern and an Interpreter pattern ( as it seems you are writing a grammar ).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top