[英]Generics and selecting correct interface implementation at runtime
我正在研究 ES & CQRS 系统的 PoC。
我定义了以下类来表示命令和事件,这些命令和事件代表正在处理的命令的 output
public class CreateEstateCommand extends Command {}
public class ChangeEstateOwnerCommand extends Command {}
public class EstateCreatedEvent extends DomainEvent {}
public class EstateOwnerChangedEvent extends DomainEvent {}
这些命令在实现以下接口的类中处理
/**
* Specific command handlers define what logic should be carried out during handling a command of type C.
* Single command execution results in an outcome of domain event of type E
*/
public interface CommandHandler<C extends Command, E extends DomainEvent> {
E handleCommand(C command);
}
public class EstateCreatedCommandHandler implements CommandHandler<CreateEstateCommand, EstateCreatedEvent> {
@Override
public EstateCreatedEvent handleCommand(CreateEstateCommand command) { /***/ }
}
public class ChangeEstateOwnerCommandHandler implements CommandHandler<ChangeEstateOwnerCommand, EstateOwnerChangedEvent> {
@Override
public EstateOwnerChangedEvent handleCommand(ChangeEstateOwnerCommand command) { /***/ }
}
现在是我想使用这些特定处理程序的部分。 命令处理的流程可以表示如下:
命令通过API进入系统,转发给CommandServce
class处理
public class CommandService {
private final EventService eventService;
private final CommandGateway commandGateway;
public void handleCommand(CreateEstateCommand command) {
EstateCreatedEvent event = commandGateway.handleCommand(command);
eventService.handleEvent(event);
}
public void handleCommand(ChangeEstateOwnerCommand command) {
EstateOwnerChangedEvent event = commandGateway.handleCommand(command);
eventService.handleEvent(event);
}
}
如您所见, handleCommand()
方法对于每个提交的命令都是重复的。 这背后的原因是我在运行时选择适当的处理程序实现时遇到的问题,具体取决于Command.commandType
:
@Service
public class CommandGateway {
private final Map<String, CommandHandler<?, ?>> commandHandlers;
@Autowired
public CommandGateway(Map<String, CommandHandler<?, ?>> commandHandlers) {
this.commandHandlers = commandHandlers;
}
public EstateCreatedEvent handleCommand(CreateEstateCommand command) {
EstateCreatedCommandHandler handler = (EstateCreatedCommandHandler) commandHandlers.get(command.getCommandType());
return handler.handleCommand(command);
}
public EstateOwnerChangedEvent handleCommand(ChangeEstateOwnerCommand command) {
ChangeEstateOwnerCommandHandler handler = (ChangeEstateOwnerCommandHandler) commandHandlers.get(command.getCommandType());
return handler.handleCommand(command);
}
}
上面的片段是我无法概括的部分。 是否有可能实现CommandGateway
class,所以CommandService
可以如下所示:
public class CommandService {
public <C extends Command, E extends DomainEvent> void handleCommand(C command) {
E event = commandGateway.handleCommand(command);
}
}
并提供类型安全的对象?
根本问题是 map,其值是通配符类型的,即实际上没有类型,更具体地说,没有键入以与键对齐。
您已经通过信任注入映射的条目而破坏了一些类型的安全性,因此只需使用原始的CommandHandler
,它将接受任何命令,并使用未经检查的强制转换以获得正确键入的返回值:
@SuppressWarnings({"unchecked", "rawtypes"})
public <C extends Command, E extends DomainEvent> E handleCommand(C command) {
CommandHandler handler = commandHandlers.get(command.getCommandType());
return (E)handler.handleCommand(command);
}
@SuppressWarnings
添加了这样您的 IDE 和构建抱怨。
虽然这看起来很残酷,但实际上您并没有失去任何类型安全性。 当您像您一样键入 map 时,这会丢失,不幸的是,鉴于 map 键入不会将值类型绑定到键类型,这是不可避免的。
如果你做这样的事情怎么办:
static abstract class Command {
public abstract String getCommandType();
public abstract Class<? extends DomainEvent> type();
}
你的实现:
public class CreateEstateCommand extends Command {
@Override
public String getCommandType() {
return null; // whatever here
}
@Override
public Class<EstateCreatedEvent> type() {
return EstateCreatedEvent.class;
}
}
用法是:
public DomainEvent handleCommand(Command command) {
return command.type().cast(commandHandlers.get(command.getCommandType()));
}
使用 generics 实现这种端到端的困难在于我们将运行时类型决策与编译时类型检查混合在一起。 您的设计接近于 MVC 设计,其中处理程序是在运行时检索的。 因此,在编译时只能进行有限的类型安全检查。
让我们看一个流程:
CreateEstateCommand
或ChangeEstateOwnerCommand
CommandHandler
。 现在,我们必须在运行时执行此操作的真正原因是,由于使用Map
的设计,无法在编译时做出决定。 如果您在CommandGateway
中有以下方法,则永远无法确定从服务 class 传递的Command
实例是否真的是映射的CommandHandler
想要的特定子项。 因此,它不会编译。
public DomainEvent handleCommand( Command command ) {
CommandHandler<? extends Command, ? extends DomainEvent> cmdHandler = commandHandlers.get(command.getCommandType());
return cmdHandler.handleCommand( command );
}
因此,您必须取消端到端类型安全要求或为您提供CommandHandler
实例的“工厂”样式 map。 省略后者将意味着 API 方法一开始就知道它需要哪个CommandHandler
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.