Keller State without a State Context - ThorbenKuck/Keller GitHub Wiki

Concept

The idea behind creating a StatePattern without a StateContext is, that every state defines its following state. For that, a State can provide 4 steps along the way:

  • The State Action
    This is the action, that the state seeks to do
  • The State Transition
    Defining when the next State is going to be created
  • The Next State
    This is the following state, defined by the current state
  • The Tear Down After the State Transition is finished and the Next State is created, before the next State Action will be executed.

Only the State Action is mandatory. without an action, the State cannot exist. Everything else can be introduced automatically.

  • No State Transition means instant, after the action is finished
  • No Next State means, the current State is the last one
  • No Tear Down means, nothing to do, if the state is done

This 4 stepp process is implemented in keller-state. Keller State works with annotations instead of interfaces. This means, you do not have to adjust your existing code (heavily). You just annotate the Methods, to do the 1 - 4 Steps needed.

Using Keller State

Step 1, The State Action

For using the State pattern, let's first create an arbitrary class, that is simply called Foo, which is some sort of Gui-Handling-Logic-Class (i am making this up, sorry for the bad example)

class Foo {

    private GUI gui;
    // some other atributes
   
    private void calculateAndDisplayCurrentAmount() {
        // ...
    }

    // some other method ..
}

This class controlls an GUI element which does something. Inside this Business logic we also have a way of displaying the "NextWindow", once we are finished with the current one (most logically using callbacks). So, in a sort, this Object is the business Logic, which talks with the Presenter of a MVP-Structured Application. I am so sorry for my bad example ..

Anyways. We now want to say, that the calculateAndDisplayCurrentAmount function, our State Action is. We can simply do this, by adding the @StateAction annotation to it, like this

class Foo {

    private Presenter presenter;
    // some other atributes
   
    @StateAction
    private void calculateAndDisplayCurrentAmount() {
        presenter.setAmount(getCurrentAmount());
        presenter.updateView();
        // ...
    }

    // some other method ..
}

Inside our Main-Class, we would now simply create a StateMachine instead of calling this Method by ourself. We do this the following way

class Main {
    public static void main(String[] args) {
        final StateMachine stateMachine = StateMachine.create();
        stateMachine.start(new Foo());
    }
}

The result would be, that the StateMachine would call the methode calculateAndDisplayCurrentAmount, which show the view and set the current amount.

If multiple StateActions are provided, the StateMachine will use the first it finds.

Step 2, The State Transition

After we calculate the current amount, we want to signal the StateMachine to wait for 2 Seconds and than recalculate it. This is a verry basic example, but it should prove the point. I am later going to explain how a non-timer based State Transition would look like.

So, what do we have to do? In our example, if we wanted such behaviour, we actually would only have to change our existing method. Since i did not provide an example, i will add a new Method here:

class Foo {

    private Presenter presenter;
    // some other atributes
   
    @StateAction
    private void calculateAndDisplayCurrentAmount() {
        presenter.setAmount(getCurrentAmount());
        presenter.updateView();
        // ...
    }

    @StateTransitionFactory
    private StateTransition transition() {
        return StateTransition.openAsTimer(2, TimeUnit.SECONDS);
    }

    // some other method ..
}

Now, our example would look a bit different. Well, not from an execution point of view, but from the behaviour. Instead of existing instantly after the view is updated, the StateMachine would wait for 2 Seconds before exiting.

Since a timer is nearly never what you want, you would normaly utilize the StateTransition#hook(Synchronize) method, which allows you to hook the StateTransition to a Synchronize, a synchronization mechanism provided by keller-core. This allows you to controll, when your State is finished and the next State should be requested and "executed".

However, if you are using the StateMachine, you might also be interrested in changing the state outside of the State (like if you want to force the next State). You can manipulate the StateMachine, to finish the current StateTransition instantly by stating the following:

StateMachine stateMachine = StateMachine.create();
stateMachine.start(new Foo());
// Some other stuff
stateMachine.step();

Calling StateMachine#step will finish the current StateTransition and continue to the next State.

Step 3, The Next State

In this step, we want to provide the follow-state. This step is called either if you finished the StateTransition, or StateMachine#step was called.

Our Application now wants to provide the follow-state. Since we used a timer as our StateTransition, we want to accomplish the following: Every 2 Seconds we want to automatically update the view.

This can be done, by providing a fully cyclic, self refferencing state (i.e. the Next State is the Current State). This is done verry easy like this

class Foo {

    private Presenter presenter;
    // some other atributes
   
    @StateAction
    private void calculateAndDisplayCurrentAmount() {
        presenter.setAmount(getCurrentAmount());
        presenter.updateView();
        // ...
    }

    @NextState
    private Object nextState() {
        return this;
    }

    @StateTransitionFactory
    private StateTransition transition() {
        return StateTransition.openAsTimer(2, TimeUnit.SECONDS);
    }

    // some other method ..
}

Whatever Method is annotated with @NextState will be requested to construct the next state, once the StateTransition is finished. In our example, we now would do the following:

We calculate the current amount and display ist and tell the StateMachine to wait for 2 Seconds. After those 2 Seconds, the same Object will be set as the new state, which in fact means: We calculate the current amount and display ist and tell the StateMachine to wait for 2 Seconds. rins repeat.

Of course we could provide anything here. Any Object with a Method, annotated with @StateAction would be suitable to be returned. In our example, let's actually return a Bar class, which would than instantly return a new Foo-instance as the next State. For that, let's create the Bar class

class Bar {
    @StateAction
    public void action() {
        someCalculations();
    }

    @NextState
    public Object nextState() {
        if(someCalculationsWhereSuccessful()) {
            return new Foo();
        } else {
            return new ErrorFoo();
        }
    }
}

This is again a bit vague, but this shows, that the you can provide any State and this might be because of whatever happend in the action (or in the meantime). ErrorFoo could look like this:

class ErrorFoo {
    @StateAction
    public void diplayError() {
        // Some error message will be displayed.
    }
}

With ErrorFoo not providing a nextState, the sequence would look like this:

---Foo
|   |
|  Bar
|   |
|  / \
| /   \
Foo   ErrorFoo

So, if anything goes well, we start in Foo, go to Bar after 2 Seconds and instantly back to Foo. But if the calculation in Bar where not correctly, we go from Bar to ErrorFoo instead.

Step 4, The Tear Down

This is more vague than anything else. The Tear Down is nothing i could associate with the provided example, but let's imagine, we would need to do something, if our Foo State is finished. Maybe hide the window. This would be extremely bad in terms of good front end design, but let's do it anyways. For Science!

class Foo {

    private Presenter presenter;
    // some other atributes
   
    @StateAction
    private void calculateAndDisplayCurrentAmount() {
        presenter.showView();
        presenter.setAmount(getCurrentAmount());
        presenter.updateView();
        // ...
    }

    @NextState
    private Object nextState() {
        return this;
    }

    @TearDown
    private void hideCurrentWindow() {
        presenter.closeView();
    }

    @StateTransitionFactory
    private StateTransition transition() {
        return StateTransition.openAsTimer(2, TimeUnit.SECONDS);
    }

    // some other method ..
}

In the Method hideCurrentWindow, we tell the Presenter to close the view and to clean up after itself. Well, thats it. In our current example, the following would happen:

  1. We show the view, calculate the current Value, display it and update the view
  2. We stay for 2 Seconds and do nothing
  3. After 2 Seconds, the StateMachine would ask us, to create the next State, we return ourself as the next State
  4. The View would be closed
  5. go to 1.

This would be extremely dumb, since effectively a windows opens for 2 Seconds with a number in it, than closes and reopens itself again. But this is just an example. Next up is dependency Management. We assumed that our methods do not need anything, but we want to change this now.

Dependency Management

keller-state is founded on keller-di, which means, that you can use any of the keller-di Annotatios in here. All Method-Arguments will be requested, instantiated and cached with keller-di. The dependencies are hereby managed by 2 Layers.

Before jumping into those, let's imagine a use for our application. Maybe, we want to calculate some Hash-Value and for that, we need a class called Hasher. We expect it in our StateAction method.

class Foo {

    private Presenter presenter;
    // some other atributes
   
    @StateAction
    private void calculateAndDisplayCurrentAmount(Hasher hasher) {
        presenter.setAmount(hasher.nextValue());
        presenter.updateView();
        // ...
    }

    // some other method ..
}

Basically, injecting dependencies work the following way:
Once the State is provided, that StateMachine tries to analyze the Class of the provided State, to find the needed Methods and translates those into an InteralState instances, which cache reflection data.
Once our Method should be executed, the StateMachine requests all required Parameters (here only the Hasher) from the keller-di DependencyManager. Now for the two Layers.

Layer 1, Preconstructed Dependencies

If you have Preconstructed Dependencies, you win. This is way more efficient in terms of computatinal power. Preconstructed dependencies bypass a lot of reflection. No new Instances have to be created, which means, no Constructor analyzation, no recursive dependency construction, all in all little overhead (there still is some!). For that, let's imagine, our Hasher has to be singleton inside all States. We now provide a Preconstructed Dependecy in the following way:

class Main {
    public static void main(String[] args) {
        final StateMachine stateMachine = StateMachine.create();
        stateMachine.addStateDependency(new Hasher());
        stateMachine.start(new Foo());
    }
}

If we where to execute this StateMachine, every StateMethod (@StateAction, @StateTransitionFactory, @NextState, @TearDown) that requires a Hasher instance would receive the provided hasher. So, in the following example, that @StateAction method would always receive the same Hasher.

class Foo {

    private Presenter presenter;
    // some other atributes
   
    @StateAction
    private void calculateAndDisplayCurrentAmount(Hasher hasher) {
        presenter.setAmount(hasher.nextValue());
        presenter.updateView();
        // ...
    }

    @NextState
    Object nextState() {
        return this;
    }

    // some other method ..
}

Even tho, in this example, we would create an infinite loop with no noticable delay.

Layer 2, On The Fly Construction

Since keller-di provides us with a way of instantiating objects, if we have the need to always have a different Hasher (maybe a hasher creates its own salt), we could just let keller-di create the Hasher. For that, we actually just need to do nothing. Our Foo class would look the same and our Main class would look like this:

class Main {
    public static void main(String[] args) {
        final StateMachine stateMachine = StateMachine.create();
        stateMachine.start(new Foo());
    }
}

which is also the same (sorry for the trick). Now, where does the Hasher, required in the @StateAction Method come from? Simply put, it is constructed at runtime. Since keller-di does not generate code at compile time, this is an overhead. To counteract this, we could reduce the Overhead, by annotating the Hasher class with the @Cache annotation. This would kick off the keller-di instance-caching.

@Cache
class Hasher {
    // .....
}

However, the result would be, that the Hasher instance is created once and never updated, which might not be what you wanted.

Of course you could use other keller-di annotations on such dependencies, for example:

@RequireNew on StateDependencies (will always create the dependency, even if it is cached)

class Foo {

    private Presenter presenter;
    // some other atributes
   
    @StateAction
    private void calculateAndDisplayCurrentAmount(@RequireNew Hasher hasher) {
        presenter.setAmount(hasher.nextValue());
        presenter.updateView();
        // ...
    }

    // some other method ..
}

@Bind annotation, if you want to bind instances to specific types

@Cache
class Hasher implements @Bind SomeInterface {
    // .....
}

@Use to signal which constructor to us on construction of Dependencies

class Hasher {
    @Use
    public Hasher() {
        // ...
    }

    public Hahser(Salt salt) {
        // ...
    }
}

And so on

⚠️ **GitHub.com Fallback** ⚠️