使用Java 15的密封sealed类优雅实现状态机 -Benji


Java 15带来了密封类的预览功能。现在,我们基于接口的状态机不仅可以防止无效转换,而且可以像枚举一样枚举。
特点是:编译时和运行时两个阶段都能检查状态切换是否合法。
 
类型检查能自动帮助检查非法状态切换
使用Java编译时的类型检查,如果我们尝试直接从绿色过渡到红色,它将无法编译:

sealed interface TrafficLight
        extends State<TrafficLight>
        permits Green, SolidAmber, FlashingAmber, Red {}
static final class Green implements TrafficLight, TransitionTo<SolidAmber> {}
static final class SolidAmber implements TrafficLight, TransitionTo<Red> {}
static final class Red implements TrafficLight, TransitionTo<FlashingAmber> {}
static final class FlashingAmber implements TrafficLight, TransitionTo<Green> {}

@Test
public void traffic_light_typechecked_example() {
    Green signal = new Green();//绿色
   
// Comment out a transition and it will fail to compile.无法编译
    signal = signal
        .transition(SolidAmber::new)
        .transition(Red::new)
        .transition(FlashingAmber::new)
        .transition(Green::new);
}

即使可能进行多个状态转换,我们仍然可以进行类型检查的转换:

static final class Pending implements OrderStatus, BiTransitionTo<CheckingOut, Cancelled> {}

pending.transition(CheckingOut::new); // fine
pending.transition(Cancelled::new);  
// fine
pending.transition(Refunded::new);  
// Compile Error

 
运行时检查非法状态切换
运行时也可以检查状态切换,如果无法切换,运行时则会抛出异常:

@Test
public void runtime_transitions_possible() {
   TrafficLight light = new Green();
   light = light
       .tryTransition(SolidAmber::new)
       .unchecked();

   assertTrue(light instanceof SolidAmber);
}
@Test(expected = State.InvalidStateTransitionException.class)
public void runtime_transitions_throw_exception_when_not_possible() {
    TrafficLight light = new Green();
    light = light
            .tryTransition(Red::new)
            .unchecked();
}

 
某个状态寻找

sealed interface OrderStatus
       extends State<OrderStatus>
       permits Pending, CheckingOut, Purchased, Shipped, Cancelled, Failed, Refunded {}
 
 
@Test public void enumerable() {
  assertArrayEquals(
    array(Pending.class, CheckingOut.class, Purchased.class, Shipped.class, Cancelled.class, Failed.class, Refunded.class),
    State.values(OrderStatus.class)
  );
 
  assertEquals(0, new Pending().ordinal());
  assertEquals(3, new Shipped().ordinal());
 
  assertEquals(Purchased.class, State.valueOf(OrderStatus.class, "Purchased"));
  assertEquals(Cancelled.class, State.valueOf(OrderStatus.class,
"Cancelled"));

提供values(), ordinal(), 和 valueOf()用于寻找状态:

static <T extends State<T>> List<Class> valuesList(Class<T> stateMachineType) {
   assertSealed(stateMachineType);
 
   return Stream.of(stateMachineType.permittedSubclasses())
       .map(State::classFromDesc)
       .collect(toList());
}
 
static <T extends State<T>> Class<T> valueOf(Class<T> stateMachineType, String name) {
   assertSealed(stateMachineType);
 
   return valuesList(stateMachineType)
       .stream()
       .filter(c -> Objects.equals(c.getSimpleName(), name))
       .findFirst()
       .orElseThrow(IllegalArgumentException::new);
}
static <T extends State<T>, U extends T> int ordinal(Class<T> stateMachineType, Class<U> instanceType) {
   return valuesList(stateMachineType).indexOf(instanceType);
}

 

更多:on github