6

我的问题是以尽可能可扩展的方式为不同的消息实现不同的行为。我知道访问者模式,我知道双重调度,但我似乎无法找到一个让我满意的解决方案(至少不在 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-hierarchy 几乎相同,所以我有点想避免反射进行方法查找,因为我害怕运行时成本。

回复评论:

  1. @aruisdante 关于@bot 建议的假设是正确的。我无法覆盖,因为如果我覆盖路由(MessageBase),我会丢失 MessageBase 的运行时类型。

  2. @aruisdante 和@geceo:我知道我可以做到这一点——这就是我所说的“切换转换”(MessageBase 有一个 MessageType 字段)——但我有 11 个实际消息类和大约 6 个我需要的代码位置,所以这将是一个巨大的痛苦实施 - 以及维护方面。

4

3 回答 3

4

以下是我过去通常解决此类问题的方法:

首先,在您的Router接口中,由于您似乎打算Router除 Composite 之外的大多数实现仅处理单个消息类型,因此将接口的定义更改为类似于:

interface Router<T extends MessageBase> {
    void route(T message);

}

这消除了为Router处理特定实现的各种 s 提供接口的需要。然后,您的派生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>与 是相同的,因此与 C++ 模板不同Router<OtherDerivedMessage>,拥有类似的东西是非法的。SuperRouter implements Router<MessageBase>, Router<OtherDerivedMessage>这也是为什么您需要传递 explisitClass<T>对象而不是仅仅能够直接从Router<T>.

于 2015-03-06T17:15:30.963 回答
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);
   } 
} 
于 2015-03-06T16:27:13.743 回答
1

在 Scala(另一种 JVM 语言)中,这听起来像是类型类的用例:

http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html

这在 Java 中是不可能的,尽管如果你在谷歌上搜索“Java 类型类”,就有一些实验性的库。

于 2015-03-06T16:03:55.910 回答