How to implement the state design pattern in a JPA domain model
I want to implement the state design pattern in JPA. The way I am currently doing this is outlined in this blog post.
The author uses an 开发者_Go百科enum containing all available state implementations instead of creating abstract class/interface for state abstraction and writing implementation for each state. I find this approach very useful, since enums can be easily serialized in JPA and you can store the current state of your object without additional effort. I also nested the state interface and all state classes into the enum making them private, since they are implementation specific and should not be visible to any client. Here's a code example of the enum:
public enum State {
STATE_A(new StateA()),
STATE_B(new StateB());
private final StateTransition state;
private State(StateTransition state) {
this.state = state;
}
void transitionA(Context ctx) {
state.transitionA(ctx);
}
void transitionB(Context ctx) {
state.transitionB(ctx);
}
private interface StateTransition {
void transitionA(Context ctx);
void transitionB(Context ctx);
}
private static class StateA implements StateTransition {
@Override
public void transitionA(Context ctx) {
// do something
ctx.setState(STATE_B);
}
@Override
public void transitionB(Context ctx) {
// do something
ctx.setState(STATE_A);
}
}
private static class StateB implements StateTransition {
@Override
public void transitionA(Context ctx) {
throw new IllegalStateException("transition not allowed");
}
@Override
public void transitionB(Context ctx) {
// do something
ctx.setState(STATE_A);
}
}
}
I'd like to and share this with you and get your thoughts on it. Do you find this useful? How would you implement the state design pattern in a JPA domain model?
Well it's an old question, but for the sake of those who might search archives - I have used spring state machine with enums (instead Strings).
Regarding handling transitions, there are annotations that allow your functions to be called when transition happens.
1.1.0.RELEASE gives a default mechanism to persist a state by persisting StateMachineContext, and an alternative using persist recipe.
Now refering to JPA - it's possible to have Entity Listener that will initialize statemachine on postload (@Postload), I think it's not good path to go.
As a corollary this AspectJ pattern combined with constant-specific Enum classes is also useful. I am not showing Spring integration here as this focuses only on AspectJ. But I guess we can use Spring with AspectJ too.
One more point is that OO patterns can be powerful for this usecase. I show this pattern only because the question points to the blog post which has a link to a Spring and AspectJ example.
And I also have a need to use good OO patterns with JPA.
public interface StateTransition {
StateTransition activate();
StateTransition deActivate();
}
public enum AStateTransition implements StateTransition{
ACTIVATE(new Activation()),
DEACTIVATE(new DeActivation());
private final StateTransition stateTransition;
private AStateTransition(StateTransition stateTransition) {
this.stateTransition = stateTransition;
}
@Override
public StateTransition activate() {
return stateTransition.activate();
}
@Override
public StateTransition deActivate() {
return stateTransition.deActivate();
}
}
public class Activation implements StateTransition {
@Override
public StateTransition activate() {
return AStateTransition.ACTIVATE;
}
@Override
public StateTransition deActivate() {
return AStateTransition.DEACTIVATE;
}
}
public class DeActivation implements StateTransition {
@Override
public StateTransition deActivate() {
return AStateTransition.DEACTIVATE;
}
@Override
public StateTransition activate() {
return AStateTransition.ACTIVATE;
}
}
@Aspect()
public class StateChangeAspect {
//Could be more generic so that all implemented methods
//are covered
@Pointcut("execution(* AStateTransition.activate()) && target(stateTransition) && if()")
public static boolean stateChangePointcut( AStateTransition stateTransition ){
return AStateTransition.ACTIVATE == stateTransition;
}
@Before("stateChangePointcut(stateTransition)")
public void test1( AStateTransition stateTransition ) {
System.out.println( " aspect " );
}
@Before("stateChangePointcut(stateTransition)")
public void test1(JoinPoint joinPoint, AStateTransition stateTransition) {
System.out.println(joinPoint + " -> " + stateTransition);
}
}
Test code :
System.out.println(AStateTransition.ACTIVATE.activate());
System.out.println(AStateTransition.DEACTIVATE.deActivate());
精彩评论