简体   繁体   中英

Java generics design problem

i want to dispatch messages to specific handlers through a common message processor

//
// Library code
//

abstract class Processor<M extends MessageHandler<? extends Message>> {
    HashMap<Class<Message>, M> handlerMap;
    void addHandler(M, Class<Message>);
    void run() {
        while(true) {
            ...
        }
    }
    // QUESTION - how to define this to include the fact that H extends M<T>
    //            actually im just trying to avoid the ugly cast in the client code.
    abstract <H extends MessageHandler<T>, T extends Message> void dispatch(H handler, T message);
}

class MessageHandler<T extends Message> {
}

class Message {
}

//
// Client code
//

class ServerMessage extends Message {
    ...
}

class ServerMessageHandler<T extends Message> extends MessageHandler<T> {
    ...
    void process(T msg, Object... params) {
        ...
    }
}

class ServerProcessor extends Processor<ServerMessageHandler<? extends Message>> {
    @Override
   <H extends MessageHandler<T>, T extends Message> void dispatch(H handler, T message) {
        // QUESTION - how do i get rid of this cast?
        ((ServerMessageHandler<T>)handler).process(T, ...);
   }
}

the server processor will be processing many different server messages, all with their own subtypes, members, etc. each one of these messages will have a separate handler. some base message classes will share handlers.

my question is how do i avoid that ugly cast in the client code? i cant seem to write the signature of the dispatch method to include the facts that we know the message handlers will be of type M (ServerMessageHandler), and that the particular ServerMessageHandler is parameterized by T, and a message of type T will be in the arguement list.

EDIT

i dont mind if the addHandler method cannot get total type safety, i can do some runtime checks to make sure the proper relationships are enforced ( i would have to change its signature to do that properly though ). my main goal here is to somehow enforce (through the signature) the two relationships in the dispatch method. that the handler being called is of type M, and that it is parameterized by T. to actually call this method there will be some unchecked casts in the run method (which in turn calls dispatch). but i dont mind having the ugliness there. just trying to move it out of the ServerProcessor.

Processor.dispatch can take any type which extends MessageHandler .

The method in ServerProcessor isn't a complete override Processor.dispatch - it will fail for handlers which aren't ServerMessageHandler instances with a class cast exception ( I'm assuming that ServerMessageHandler not extending MessageHandler is a typo rather than by design; otherwise it will fail for all inputs as no MessageHandler is a ServerMessageHandler ; otherwise just make the parameter's type ServerMessageHandler<T> ).

Why would you expect there to be a way of expressing intrinsically unsafe behaviour in a typesafe manner?

The contract for Processor.dispatch is that H can be any MessageHandler and T can be any message. If H instead can only be the type of handler Processor is parametrised by, M , then use that in the definition:

abstract class Processor<M extends MessageHandler<? extends Message>> {
    ...
    abstract <T extends Message> void dispatch (M handler, T message);
}

But again, this loses something in that M isn't related to T. There isn't anything equivalent to the unbind/bind idiom in Java, and it does seem that the dispatch method either shouldn't care about the subtype of message, or the processor should care - you seem to be maintaining a mix of message handler types by your addHandler method taking a handler for any method at runtime, then wanting to make it specific for a particular type in the dispatch method.

So does a processor handle only one message type or not? If it does, and you want type safety, then make the message type a type parameter. If it handles multiple message types decided at runtime, then you won't get compile time checking of the type.


You can move the cast if you separate the event processing loop and the mechanism to dispatch to a handler:

/**
* @param <M> message type
*/
class Processor < M > {

    Dispatcher<M> dispatcher;

    public Processor ( Dispatcher<M> dispatcher ) {
        this.dispatcher = dispatcher;
    }

    void run ( M...messages ) {
        for ( M message : messages ) {
            // as there is no mechanism in java to get from Class<T> to Foo<T>, this call
            // must be made with the wildcard H Foo<?>
            dispatcher.dispatch ( message );
        }
    }
}

interface Dispatcher<M> {
    <T extends M> void dispatch ( T message );
}

class Message {
}

class ServerMessage extends Message {
    //...
}

interface ServerMessageHandler<T extends ServerMessage> {
    //...
    void process ( T msg, String param ) ;
}

class ServerDispatcher implements Dispatcher<ServerMessage > {
    HashMap < Class < ? extends ServerMessage >, ServerMessageHandler<?> > handlerMap = new 
    HashMap < Class < ? extends ServerMessage >, ServerMessageHandler<?> > ();

    <T extends ServerMessage >
    void addHandler ( ServerMessageHandler<T> handler, Class < T > clz ) {
        handlerMap.put ( clz, handler );
    }

    @SuppressWarnings("unchecked")
    // cannot use a trick like clz.cast() as we want ServerMessageHandler<T> rather than T
    <T extends ServerMessage> ServerMessageHandler<T> getHandler ( Class < ? extends ServerMessage > clz ) {
        return ( ServerMessageHandler<T> ) handlerMap.get(clz);
    }

    @Override
    public <T extends ServerMessage>
    void dispatch ( T message ) {
        ServerMessageHandler<T> serverMessageHandler = getHandler ( message.getClass() );

        serverMessageHandler.process ( message, "wibble" );
    }
}

however, if the loop is driven from a queue of the base event type as in the Process.run() code, it will be no better in terms of type safety, as the only version of ServerDispatcher.dispatch called is with T = ServerMessage , and the cast is hidden in the getHandler() method. Safety comes from the symmetry of addHandler and getHandler , not from different bindings of the type variable T . Separating Processor and Dispatcher means only the specific Dispatcher has to know about the relationship between T and ServerMessageHandler<T> .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM