Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two-way extensible hierarchy with Java

My question is about implementing different behaviours for different messages in an as extensible way as possible. I am aware of the visitor pattern, I am aware of double-dispatch, but I can't seem to figure out a solution, which satiesfies me (not within the limits of java at least).

My situation is as follows:

I have a hierarchy of Messages:

Message Hierarchy

and a hierarchy of router-interfaces, each defining a route method for its own message-type:

Router-Interface Hierarchy

which I would like to implement similar to this:

Implementation

to be able to add and remove the capability to route certain messages, as well as to change routing-strategies for certain messages easily.

The problem is, that without switch-casting my message, which I don't want to do, I cannot select the respective function for the interface, because something like

CompositeRouter comp = new AllRouter(...//new Router instances);
MessageBase msg = new DerivedMessage();
msg.process(comp);

will lead to java selecting the overload <runtime message-type>.process(Router)

at compile time, which, at runtime, is invoked for the respective router object. So I cannot select the right calls to process() at compile time it seems. I can also not do it the other way round, because comp.route(msg)

will be resolved to <dynamic router-type>.route(MessageBase).

I could write a visitor, which selects the proper method from CompositeRouter, but therefor I would have to define the visitor interface with the respective route-Methods defined for all the MessageTypes up front, which kind of defeats the purpose, because it means that I have to rewrite the visitor whenever I add a new DerivedMessage.

Is there a way to implement this such that both Message and Router are extensible or is it hopeless given the current java-features?

Edit 1:

Something I forgot to mention is that I have 4 or 5 other situations, which are pretty much the same as the Router-hierarchy, so I kind of want to avoid Reflection for method-lookup, because I am afraid of the runtime-cost.

Response to comments:

  1. @aruisdante's assumption regarding @bot's suggestion is correct. I cannot Override, because I would loose the runtime-type of MessageBase, if I override route(MessageBase).

  2. @aruisdante and @geceo: I know that I can do that - this what I meant with "switch-casting" (MessageBase has a MessageType field) - but I have like 11 actual message classes and ~6 locations in code where I need it, so it would be a HUGE pain implementation- as well as maintenance-wise.

like image 643
midor Avatar asked Mar 06 '15 15:03

midor


1 Answers

Here is how I've typically solved problems like this in the past:

First, in your Router interface, since it seems you intend most Router implementations with the exception of the Composite to handle only a single message type, change the definition of the interface to something similar to:

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

}

This removes the need to provide interfaces for the various Routers that handle specific implementations. Your derived Router classes then become something like:

class OtherDerivedRouter implements Router<OtherDerivedMessage> {

    @Override
    void route(OtherDerivedMessage message) { //... };

}

So now what happens in CompositeRouter? Well, we do something like this:

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
     }

}

The RouterAdaptor does the heavy lifting of dispatching the correct message type expected by the Router implementation it holds. And leaves CompositeRouter needing only to store a registry of these adaptors to their message type.

The biggest downside of this approach is that, thanks to Type Erasure, there is no way to create a Router implementation that handles more than one message type by itself directly. From Java's prospective, at runtime Router<MessageBase> is the same as Router<OtherDerivedMessage>, and thus it is illegal to have something like SuperRouter implements Router<MessageBase>, Router<OtherDerivedMessage>, unlike you could with C++ templates. This is also why you need to pass explisit Class<T> objects around rather than just being able to infer the type directly from Router<T>.

like image 152
aruisdante Avatar answered Oct 21 '22 13:10

aruisdante