javadesign-patternscode-duplicationstate-pattern

State Design Pattern: How to avoid code duplication when using entry() and exit()?


Situation: My States have an entry() and exit() method that needs to be called every time when a State transitions. To ensure that, I used a changeState() method in State that contains the necessary procedure. It is called by the Context every time it uses an operation involving the state machine. The problem however, is that I need to call state.changeState() every time when I add a new method and I'm sure there's a way to avoid that code duplication. Below is code for further clarification

class Context {
    State state;

    void method1() {
        state.method1();
        state.changeState();
    }

    void method2() {
        state.method2();
        state.changeState(); // Code duplication!!
}

abstract class State {
    Context context;
    State next;

    void changeState() {
        this.exit();
        next.entry();
        context.setState(next);
    }
    // other necessary methods
}

class ConcreteState extends State {
    void method1() {
        // do something
        next = AnotherConcreteState;
    }
    void entry() {/*do something */};
    void exit() {/*do something */};
}

If I want to add additional methods in Context, what can I do to avoid code duplication of calling state.changeState() every time inside the new methods?


Solution

  • You are very close. The changeState method belongs in the Context class, not the State class. Here's a good article on the topic. Note that the class diagram shows changeState in the Document (context) class.

    To make it even cleaner, changeState could take the next state as a parameter. Like this:

    class Context {
      State state;
    
      void method1() {
        state.method1();
      }
    
      void changeState(next) {
        state.exit();
        this.state = next;
        state.enter();
      }
    }
    
    abstract class State {
      Context context;
    
      // other methods
    }
    
    class ConcreteState extends State {
      void method1() {
        // do something
        context.changeState(AnotherConcreteState);
      }
    
      void enter() { /* do something */ }
      void exit() { /* do something */ }
    }
    

    Now, as you add more methods to Context, there is no duplication in Context. It'd look like this:

    class Context {
      State state;
    
      void method1() {
        state.method1();
      }
    
      void method2() {
        state.method2();
      }
    
      void changeState(next) {
        state.exit();
        this.state = next;
        state.enter();
      }
    }
    
    abstract class State {
      Context context;
    
      // other methods
    }
    
    class ConcreteState extends State {
      void method1() {
        // do something
        context.changeState(AnotherConcreteState);
      }
    
      void method2() {
        // do something else
        context.changeState(YetAnotherConcreteState);
      }
    
      void enter() { /* do something */ }
      void exit() { /* do something */ }
    }