是否有一种干净的方法通过匹配命令和结果的通用类型来将命令委托给适当的处理程序?
public interface Handler<COMMAND, RESULT> {
public RESULT handle(COMMAND command);
}
public class Dispatcher {
// proper constructor filling the handlers list
private final List<Handler<?, ?>> handlers = new ArrayList<>();
public <COMMAND, RESULT> RESULT handle(COMMAND command) {
Handler<COMMAND, RESULT> handler;
return handlers.stream()
.filter(h -> h instanceof Handler<COMMAND, RESULT>)
.findFirst()
.get()
.handle(command);
}
构建失败说明
Handler
' 无法安全地转换为 '处理程序
这可能吗? 我想知道 DI 库是如何做到这一点的,因为这是“相似的”IMO。
因为类型擦除,
handle
方法实际上并不知道您传递给RESULT
类型参数的类型,因此无法根据结果类型选择处理程序。它也不知道 COMMAND
类型参数,但您可以 kind of 通过 command.getClass()
得到它。如果命令可以为 null,或者如果您传递的派生类比传递给 COMMAND
类型参数的类更多,这也行不通。
就是说,调用者必须传递一个
Class
,以指示结果的类别,如果command.getClass()
不够,则指示命令的类别。
现在介绍如何检索处理程序。假设您的所有处理程序直接实现接口并为
COMMAND
和 RESULT
类型参数提供实际类,则可以使用 Handler<?, ?>
从
getActualTypeArguments
列表中获取正确的处理程序,就像这样.
for (var handler : handlers) {
var typeArguments = ((ParameterizedType)handler.getClass().getGenericInterfaces()[0]).getActualTypeArguments()
if (typeArguments[0] == command.getClass() && typeArguments[1] == resultClass) {
// this handler is correct!
}
}
就是说,我更喜欢使用
Map<Pair<Class<?>, Class<?>>, Handler<?,?>>
来存储处理程序,本质上是将 COMMAND
和 RESULT
信息存储为地图键中的 Class
es。
private final Map<Pair<Class<?>, Class<?>>, Handler<?, ?>> handlers = new HashMap<>();
// always use this to add handlers
private <COMMAND, RESULT> void registerHandler(
Class<COMMAND> commandClass,
Class<RESULT> resultClass,
Handler<? super COMMAND, ? extends RESULT> handler) {
handlers.put(new Pair<>(commandClass, resultClass), handler);
}
那么
handle
可以这样实现:
@SuppressWarnings("unchecked")
// optionally, add a Class<COMMAND> parameter, instead of using command.getClass to get the command class
public <COMMAND, RESULT> RESULT handle(COMMAND command, Class<RESULT> resultClass) {
var handler = (Handler<COMMAND, RESULT>)handlers.get(
new Pair<Class<?>, Class<?>>(command.getClass(), resultClass)
);
if (handler == null) {
throw new HandlerNotFoundException(...);
}
return handler.handle(command);
}
请注意,由于这使用了
Class
es,因此它不区分例如。 List<Integer>
和 List<String>
。如果你想区分它们,调用者需要传递 Type
s(这涉及 this trick 和 helper 类)。使用地图可能不会那么有用。您只需浏览列表并手动比较Type
s。