Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to restrict status transition values to only valid values in Java?

I have to implement a status transition validation, like:

'P' -> 'W' -> 'M' -> 'F' -> 'G'
'P' -> 'I' -> 'B' -> 'M' -> 'F' -> 'G'

where "->" denotes "can move to"

In short, a particular status can move to only certain status, but not other status. As in above example, P can move to W or I but not any other status.

Note: There are finite number of status in the system.

I have read about Strategy pattern, but I did not feel this particular problem suited to it. What will be best way to implement this in Java 8 ?

like image 689
Adhyatmik Avatar asked Dec 05 '18 11:12

Adhyatmik


3 Answers

My suggestion would be to write an enum State that represents each available state and the valid transitions:

enum State {
    G(Collections.emptySet()),
    F(Collections.singleton(G)),
    M(Collections.singleton(F)),
    B(Collections.singleton(M)),
    I(Collections.singleton(B)),
    W(Collections.singleton(M)),
    P(new HashSet<>(Arrays.asList(W, I)));

    private final Set<State> validTransitions;

    State(final Set<State> validTransitions) {
        this.validTransitions = validTransitions;
    }

    public State transitionTo(final State next) {
        if(!validTransitions.contains(next)) {
            throw new IllegalStateException();
        }
        return next;
    }
}

NB:

This will only allow for a DAG of state transitions, if you reference a State in validTransitions that hasn't yet been declared then you will get an "Illegal forward reference" compiler error.

I see this as an advantage as it will enforce a valid set of states at compile time, but only if your states are acyclic.


If you are using Java 9+

enum State {
    G(Collections.emptySet()),
    F(Set.of(G)),
    M(Set.of(F)),
    B(Set.of(M)),
    I(Set.of(B)),
    W(Set.of(M)),
    P(Set.of(W, I));

    private final Set<State> validTransitions;

    State(final Set<State> validTransitions) {
        this.validTransitions = validTransitions;
    }

    public State transitionTo(final State next) {
        if(!validTransitions.contains(next)) {
            throw new IllegalStateException();
        }
        return next;
    }
}
like image 200
Boris the Spider Avatar answered Nov 14 '22 22:11

Boris the Spider


There is the state pattern, and of course, you could also think of implementing a complete state machine yourself.

You specify within code which events allow specific state transitions.

In other words: you don't think in terms of "black listing" (P can't go to B), but instead you explicitly state (P can go to W and I, period).

For further reading, see here for a discussion of state pattern vs state machine.

like image 38
GhostCat Avatar answered Nov 14 '22 21:11

GhostCat


I have had a requirement like this. This is how I solved it. The idea is to maintain an enum and process the next states only if they are valid.

 public enum State {
    P {
        @Override
        public Boolean isValid(State nextState) {
            return nextState==State.W || nextState==State.I;
        }
    },
    W {
        @Override
        public Boolean isValid(State nextState) {
            return nextState==State.M;
        }
    },
    M {
        @Override
        public Boolean isValid(State nextState) {
            return nextState==State.F;
        }
    },
    I {
        @Override
        public Boolean isValid(State nextState) {
            return nextState==State.B;
        }
    },
    B {
        @Override
        public Boolean isValid(State nextState) {
            return prevState==State.M;
        }
    },
    G {
        @Override
        public Boolean isValid(State nextState) {
            return false;
        }
    },
    F {
        @Override
        public Boolean isValid(State nextState) {
            return nextState==State.G;
        }
    };

    public abstract Boolean isValid(State nextState);
    }
like image 39
Phenomenal One Avatar answered Nov 14 '22 23:11

Phenomenal One