5

I am using the State Pattern, but the examples I found are for educational purpose and I wanted to know what are best practices in this pattern for sharing objects between states, i.e. a boolean, a List and also for one state object to change the state reference in the automata object.

I will lay down a simple model to be used as an example.

public abstract class State {

    Automata automata;

    public State( Automata automata){
         this.automata = automata;
    }

    public abstract void action();

}

public class State1 extends State {

    public State1(Automata automata){
         super(automata)
    }

    @Override
    public void action() {
        // GET  : Use obj1 
        // POST :Set in automata state to State2 
    }
} 

public class State2 extends State {

     public State2(Automata automata){
         super(automata)
    }

    @Override
    public void action() {
        // GET  :Use obj1 
        // POST :Set in automata state to State1 
    }
}  

public class Automata {

     private State state1 = new State1(this);
     private State state2 = new State2(this);
     private State state = state1;

     public void takeAction() {
         state.action()
     }
}

I have tried the following solutions:

  1. For GET store obj1 in Automata and use getters and setters. For POST store states in Automata and use getters and setters. This approach will make the code unnecessarily long by using the getters and becomes hard to maintain with a changing list of obj1's and states.
  2. Use private inner classes. Declare State, State1 and State2 as private inner classes and access directly obj1 and states. Hard to maintain and update simply because of length of file. Can not be shared with another Automata class.
  3. Make fields public. I do not want to expose all of this fields.
  4. Use a singleton/static class approach for sharing obj1's

I am not very found of package private access.

In my design I am combining this pattern with the Template Method Pattern as a side information.

I know a one-fits-all approach does not exist, but what are common best practices in working with this pattern?

jaco0646
  • 15,303
  • 7
  • 59
  • 83
Radu Ionescu
  • 3,462
  • 5
  • 24
  • 43
  • What do you mean by *sharing objects between states* ? States are supposed to be mutually exclusive, their lifecycles shouldn't overlap. One thing you can do is transmit an object when transitioning from state A to state B, but not much more. – guillaume31 Jan 26 '16 at 11:26
  • Any design in which you need to account for a history of past events/actions, all the states should be able to read and write to the history. I am not saying your approach is wrong only that if the total history consists of 10 object and any two states si and sj would have an intersection of a fixed amount of objects (i.e. s1 uses obj1 and obj2 s2 uses obj2 and obj3....s10 uses obj10 and obj1), you would need to pass a long list of parameters which makes the code unreadable. Furthermore it does not solve s1 which want to transition to state s2 to update the state of automata to the correct ref – Radu Ionescu Jan 26 '16 at 12:01
  • You're not describing a State pattern but a kind of [Memento](https://en.wikipedia.org/wiki/Memento_pattern) pattern to the nth degree. You should reflect that in your Q, as it is far from a typical use of State. – guillaume31 Jan 26 '16 at 13:06
  • 1
    Also, as @rinde rightly pointed out, the best option is not to have the state mutate its host but to make it return a new state. The host then transitions to this new state by mutating itself. – guillaume31 Jan 26 '16 at 13:11
  • 1
    TBH, without friend-declarations and without inner classes (why not?), `package protected` fields seems to be the shortest way for exposing shared data to only concerned states. Maybe put the state machine in a dedicated package. – Markus Kull Jan 26 '16 at 13:19
  • The problem I have with states "sharing objects" is that there's a high risk of accidentally mutating another state if you change something in one state. Having an integral version for each state at least maintains some kind of separation between them. Java might not provide the best tools for instantiating a slightly different state based on a previous one (I hear optional parameters don't exist in that language) but it should be roughly as verbose as maintaining direct pointers between states - and better a verbose robust solution than a concise fragile one anyway. – guillaume31 Jan 26 '16 at 13:23
  • True, shared data (aka global variables) should be avoided. Shared data is more acceptable if it is managed by a (minimal) state machine with (only) defined transitions. – Markus Kull Jan 26 '16 at 13:43
  • To get rid of boilerplate code i can recommend SMC of http://smc.sourceforge.net/ . Makes the statemachine way more concise. Also allows surefire entry/exit actions, which go a long way towards managing resources as in finally-blocks. For shared data, SMC uses `ctx`-variable together with package-protected attributes (or friends in other languages). – Markus Kull Jan 26 '16 at 13:44
  • In my actual code I went for inner classes since the shared variables (knowledge) are around 9 and states are around 7 with as many as 4 actions each that need to be implemented. In this way it was simpler to use obj1.doSomething() in state actions than to have getObj1().doSomething(). The algorithm changes a lot and I wanted to move them outside and manage them easily without the need to complicate code in actions with getters and setters. Also keep different implementations. I am interested in a solution to make my life easier :) – Radu Ionescu Jan 26 '16 at 14:17
  • @guillaume31 Automata is just a name I gave since this is how the State Pattern is usually presented in educational materials. – Radu Ionescu Jan 26 '16 at 14:18
  • @MarkusKull I think I would go for this kind of solution. Can you post a small answer to be easily available if someone finds this question in the future. – Radu Ionescu Jan 26 '16 at 14:19
  • 1
    @MarkusKull This is not about global variables. It is about each object protecting and hiding its own internal state - one of the basic OO principles. The State pattern is a good opportunity to add a functional-ish twist to this and benefit from immutability since in principle you only replace a `State` with another, you don't mutate things inside a `State`. Tainting this with bad OO where multiple State objects have shared mutable fields defeats it all IMO. – guillaume31 Jan 26 '16 at 14:35
  • @RaduIonescu I'm not talking about the name `Automata`. I'm talking about the fact that your Q implies 1) that an object can have multiple coexisting states and 2) that they can share things. You never see that in canonical State pattern educational examples. – guillaume31 Jan 26 '16 at 14:41
  • @guillaume31 if you have to write 3000 of lines of code and then replace every variable in it with a bunch of delegation it makes sense to want to mutate it in the state(i.e. replace obj1 with someRef.getObj1() or instead of obj1++ to have someRef.setObj1(someRef.getObj1()+1)),etc – Radu Ionescu Jan 26 '16 at 14:47
  • @guillaume31 and adding that this 3000 lines of code are subject to great change. – Radu Ionescu Jan 26 '16 at 14:48
  • @RaduIonescu What 3000 lines of code ? You should be able to new up a `State` as a slight variation of a previous state (the *"intersection between two states"* you mentioned). I know how I would do it in C# or F# and it would be very simple (copy-and-update) but in the absence of optional parameters in Java I have no immediate answer to give you. If I have time I'll investigate on how it can be done in java. – guillaume31 Jan 26 '16 at 16:19
  • @guillaume31 did you think I was just trying to split an if statement in classes? :) This is what the algorithm is. It has states, it some input and depending on input and history it does some work and can decide to switch state or remain in the same state – Radu Ionescu Jan 26 '16 at 17:56

2 Answers2

4

I would do something like this:

public class Automata {
  List<String> someList;
  boolean someBoolean;
  private State currentState;

  public void performAction() {
    currentState = currentState.action(this);
  }

  interface State {
    State action(Automata context);
  }

  enum States implements State {
    IDLE {
      @Override
      public State action(Automata context) {
        if (context.someBoolean) {
          return WORKING;
        }
        return this;
      }
    },
    WORKING {
      @Override
      public State action(Automata context) {
        if (context.someList.size() > 7) {
          return IDLE;
        }
        return this;
      }
    }
  }
}

The setting of the state is now done in the performAction() of Automata, no need to do this from within each state. I used enum as the implementations of the state because they are great when you want to implement the state as a pure function. However, if your states themselves are not stateless you may want to implement the states as static inner classes.

rinde
  • 1,181
  • 1
  • 8
  • 20
  • I like the code you written, but it basically hits the same wall of 1 in my list if states are moved outside of class Automata. If states need a variable and you use inner classes, it is option 2 in my list. However ***passing a context in the action** is a better option that through the constructor as you pointed out. Is it a good idea or possible for the context to keep reference to shared state which is implemented as a singleton inner class in the State thus accessing all fields? – Radu Ionescu Jan 26 '16 at 12:14
  • As long as you keep the states in the same package as Automata it should work fine. However, it may be cleaner if you define Automata as an interface with some specific methods for reading some variables and some methods for executing actions. Then you can have ConcreteAutomata and SpecialConcreteAutomata all use the same states but with possibly different behaviour (and states can be placed in any package). – rinde Jan 26 '16 at 12:38
  • So if `SharedState`(or `AutomataBaseClass`) and `State,State1,State2` are in the same package and you declare fields with package private you can then have the same behaviour as in my answer, but moving the class `SharedState` outside of class `State` into the package? – Radu Ionescu Jan 26 '16 at 12:46
  • The *same behaviour*, yes maybe, but your question was about the *design* of the state machine, which is considerably different in my opinion. – rinde Jan 26 '16 at 12:55
1

Following @rinde post, I am curious if this is an acceptable solution

public abstract class State {

    public State(){
    }

    public abstract void action(sharedState);

    public class SharedState{
         Obj1 obj1;
         State state1;
         State state2;
         State curState;

         //Getters and setters
    }
}

public class State1 extends State {

    @Override
    public void action(SharedState sharedState) {
        // GET  : Use obj1 
        sharedState.obj1.doSomething()
        // POST :Set in automata state to State2 
        sharedState.curState = sharedState.state2;
    }
} 

//..same for State2

public class Automata {

   State.SharedState sharedState;

   public Automata(){
        sharedState.setState1(new State1());
        sharedState.setState2(new State2());
   }


   public void takeAction() {
       sharedState.getCurrentState().action(sharedState);
   }
}
Radu Ionescu
  • 3,462
  • 5
  • 24
  • 43
  • 1
    I'm not sure why you think you need `SharedState`? It seems redundant to me. Also I think it is better to only let `Automata` itself change the the current state (I made it private in my answer). – rinde Jan 26 '16 at 12:48
  • 1
    In my actual code I went for inner classes since the shared variables (knowledge) are around 9 and states are around 7 with as many as 4 actions each that need to be implemented. In this way it was simpler to use `obj1.doSomething()` in state actions than to have `getObj1().doSomething()`. The algorithm changes a lot and I wanted to move them outside and manage them easily without the need to complicate code in actions with getters and setters. Also keep different implementations. I am interested in a solution to make my life easier :) – Radu Ionescu Jan 26 '16 at 12:59