我的问题是以尽可能可扩展的方式为不同的消息实现不同的行为。我知道访问者模式,我知道双重调度,但我似乎无法找到一个解决方案,这让我感到困惑(至少不在java的范围内)。
我的情况如下:
我有一个消息层次结构:
和路由器接口的层次结构,每个都定义了自己的消息类型的路由方法:
我想实现类似于此:
能够添加和删除路由某些消息的功能,以及轻松更改某些消息的路由策略。
问题是,没有切换我的消息,我不想做,我不能选择相应的界面功能,因为像
CompositeRouter comp = new AllRouter(...//new Router instances);
MessageBase msg = new DerivedMessage();
msg.process(comp);
将导致java选择重载<runtime message-type>.process(Router)
在编译时,在运行时,为相应的路由器对象调用。所以我似乎无法在编译时选择正确的process()调用。我也不能反过来做,因为comp.route(msg)
将被解析为<dynamic router-type>.route(MessageBase)
。
我可以编写一个访问者,它从CompositeRouter中选择正确的方法,但是因此我必须使用相应的路由定义访问者接口 - 预先为所有MessageTypes定义的方法,这种方法会失败,因为它意味着每当我添加新的DerivedMessage时我都必须重写访问者。
有没有办法实现这一点,消息和路由器都是可扩展的,或者考虑到当前的java特性,它是否无望?
编辑1:
我忘了提到的是我有4或5个其他情况,它们与Router
- 层次结构几乎相同,所以我有点想避免使用Reflection进行方法查找,因为我是害怕运行时成本。
对评论的回应:
@ aruisdante关于@ bot的建议的假设是正确的。一世 不能覆盖,因为我会松开运行时类型 MessageBase,如果我覆盖路由(MessageBase)。
@aruisdante和@geceo:我知道我可以这样做 - 这就是我所说的“switch-casting”(MessageBase有一个MessageType字段) - 但我有11个实际的消息类和~6个位置在我的代码中 需要它,所以这将是一个巨大的痛苦实施 - 以及 维护,明智的。
答案 0 :(得分:4)
以下是我过去常常解决这类问题的方法:
首先,在你的Router
界面中,因为除了Composite之外,你似乎打算大多数Router
实现只处理单个消息类型,所以将接口的定义更改为类似于:
interface Router<T extends MessageBase> {
void route(T message);
}
这消除了为处理特定实现的各种Router
提供接口的需要。您派生的Router
类将变为类似:
class OtherDerivedRouter implements Router<OtherDerivedMessage> {
@Override
void route(OtherDerivedMessage message) { //... };
}
那么现在CompositeRouter
会发生什么?好吧,我们这样做:
class CompositeRouter implements Router<MessageBase> {
protected static class RouterAdaptor< T extends MessageBase> implements Router<MessageBase> {
private Router<T> router;
private Class<T> klass;
RouterAdaptor(Router<T> router, Class<T> klass) {
this.router = router;
this.klass = klass;
}
@Override
public void route(MessageBase message) {
try {
router.route(klass.cast(message));
} (catch ClassCastException e) {
// Do whatever, something's gone wrong if this happens
}
}
}
private Map<Class<?>, RouterAdaptor<?>> routerMap;
@Override
public void route(MessageBase message) {
RouterAdaptor<?> adaptor = routerMap.get(message.getClass());
if (adaptor != null) {
adaptor.route(message)
} else {
// do your default routing case here
}
}
public <T extends MessageBase> void registerRouter(Router<T> router, Class<T> klass) {
// Right now don't check for overwrite of existing registration, could do so here
routerMap.put(klass, new RouterAdaptor<T>(router, kass));
}
CompositeRouter(/*...*/) {
//initialize routerMap with Map type of choice, etc
}
}
RouterAdaptor
完成了调度它所拥有的Router
实现所期望的正确消息类型的繁重工作。并且CompositeRouter
只需要将这些适配器的注册表存储到它们的消息类型中。
这种方法的最大缺点是,由于Type Erasure,没有办法创建一个Router
实现,它直接处理多个消息类型。从Java的角度来看,在运行时Router<MessageBase>
与Router<OtherDerivedMessage>
相同,因此像SuperRouter implements Router<MessageBase>, Router<OtherDerivedMessage>
这样的东西是非法的,与C ++模板不同。这也是为什么你需要传递explisit Class<T>
对象而不是直接从Router<T>
推断出类型的原因。
答案 1 :(得分:2)
您可以拥有Router
实施的“注册表”及其相应的消息类型。
class CompositeRouter implements Router {
private Map<Class,Router> registry = new HashMap<>()
void registerRouter(Class<? extends MessageBase> messageClass, Router router) {
register.put(messageClass, router);
}
@Override
void process(MessageBase message) {
// here you can implement more sophisticated logic
// to find most appropriate Router for given message according
// type hierarchy
Router router = registry.get(message.getClass());
router.process(message);
}
}
答案 2 :(得分:1)
在Scala(另一种JVM语言)中,这听起来像是类型类的用例:
http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html
这在Java中是不可能的,尽管如果你google&#34; Java类类&#34;它有一些实验库。