您还可以将访问者模式改编为枚举,从而避免在枚举类中放置各种不相关的状态。
如果修改枚举的人足够小心,就会发生编译时失败,但不能保证。
在默认语句中,您仍然会在 RTE 之前出现故障:当加载访问者类之一时它会失败,您可以在应用程序启动时发生这种情况。
这是一些代码:
你从一个看起来像这样的枚举开始:
public enum Status {
PENDING, PROGRESSING, DONE
}
以下是您如何将其转换为使用访问者模式:
public enum Status {
PENDING,
PROGRESSING,
DONE;
public static abstract class StatusVisitor<R> extends EnumVisitor<Status, R> {
public abstract R visitPENDING();
public abstract R visitPROGRESSING();
public abstract R visitDONE();
}
}
当您向枚举添加一个新常量时,如果您没有忘记将方法 visitXXX 添加到抽象 StatusVisitor 类中,那么您将直接在您使用访问者的任何地方都出现编译错误(它应该替换您所做的每个开关在枚举上):
switch(anObject.getStatus()) {
case PENDING :
[code1]
break;
case PROGRESSING :
[code2]
break;
case DONE :
[code3]
break;
}
应该变成:
StatusVisitor<String> v = new StatusVisitor<String>() {
@Override
public String visitPENDING() {
[code1]
return null;
}
@Override
public String visitPROGRESSING() {
[code2]
return null;
}
@Override
public String visitDONE() {
[code3]
return null;
}
};
v.visit(anObject.getStatus());
现在是丑陋的部分,EnumVisitor 类。它是访问者层次结构的顶级类,如果您忘记更新抽象访问者,则实现访问方法并使代码在启动(测试或应用程序)时失败:
public abstract class EnumVisitor<E extends Enum<E>, R> {
public EnumVisitor() {
Class<?> currentClass = getClass();
while(currentClass != null && !currentClass.getSuperclass().getName().equals("xxx.xxx.EnumVisitor")) {
currentClass = currentClass.getSuperclass();
}
Class<E> e = (Class<E>) ((ParameterizedType) currentClass.getGenericSuperclass()).getActualTypeArguments()[0];
Enum[] enumConstants = e.getEnumConstants();
if (enumConstants == null) {
throw new RuntimeException("Seems like " + e.getName() + " is not an enum.");
}
Class<? extends EnumVisitor> actualClass = this.getClass();
Set<String> missingMethods = new HashSet<>();
for(Enum c : enumConstants) {
try {
actualClass.getMethod("visit" + c.name(), null);
} catch (NoSuchMethodException e2) {
missingMethods.add("visit" + c.name());
} catch (Exception e1) {
throw new RuntimeException(e1);
}
}
if (!missingMethods.isEmpty()) {
throw new RuntimeException(currentClass.getName() + " visitor is missing the following methods : " + String.join(",", missingMethods));
}
}
public final R visit(E value) {
Class<? extends EnumVisitor> actualClass = this.getClass();
try {
Method method = actualClass.getMethod("visit" + value.name());
return (R) method.invoke(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
有几种方法可以实现/改进这个胶水代码。我选择向上走类层次结构,当超类是 EnumVisitor 时停止,然后从那里读取参数化类型。您也可以使用作为枚举类的构造函数参数来做到这一点。
你可以使用更聪明的命名策略来减少丑陋的名字,等等......
缺点是它有点冗长。好处是
- 编译时错误[无论如何在大多数情况下]
- 即使您不拥有枚举代码也可以使用
- 无死码(switch on all enum values的默认语句)
- sonar/pmd/... 不抱怨你有一个没有默认语句的 switch 语句