tags:

views:

97

answers:

3

Hi,

I have the following "generic" question

Here is a my generic message interface.

public interface Message<P,R> {  
    void setParams(P Params); // and the corresponding getter  
    void setResults(R results);  
}  

For simplicity reasons I would like to specify a number (of interfaces) of messages by predefining the parameter and result type. So that if you work with message X you know what the params and result type are.

Such as

public interface MyMessage extends Message<String,String> {}  

That works fine, now I would like to have a common base class which can implement any of the messages.

public class BaseMessage<P,R> implements Message<P,R> {
    P param;
    R results;
    // base implementation
}

The problem of the above now is that I can't have a BaseMessage which implements MyMessage.

Multiple attempts like

public class BaseMessage<T extends BaseMessage<P,R>> implements Message<P,R> 

or

public class BaseMessage<? extends BaseMessage<P,R>> implements Message<P,R> 

will not work, as the compiler complains that P and R are not bound. Introducing them as additional parameters works again, but at that moment the resulting class will not be cast-able to BaseMessage or the desired MyMessage.

public class BaseMessage<P,R,T extends BaseMessage<P,R>> implements Message<P,R> 

I would like to have a generic factory method, which would be given the message class, the parameter and the result object and will return an instance of BaseMessage which implements the MyMessage interface. Like in the follwing

public static <P, R, T extends Message<P,R>> T factory(Class<T> clazz,P p,R r) {
    T a = (T) new BaseMessage<P,R>();
    // create a BaseMessage which which Implements T
    a.setParams(p);
    a.setResults(r);
    return a;
}

Anyone encountered a similar problem, or is that something not possible in Java?

Thanks!

+2  A: 

I am afraid such a generic factory is not possible, because you can't instantiate generic types like

T a = new BaseMessage<T>();

You have to tell the compiler the exact generic type parameters.

What you can do is create instances of each concrete message type, and store them in a map with the corresponding class tokens as key. Then the factory method can look up the right message and either return it directly, or (if you need separate instances) use the message object as a prototype to create the instance to be returned.

We have a somewhat similar design in our current project, with the difference that the concrete subclasses are each existing classes, with a distinct name and (unfortunately largely - but not fully - duplicated) implementation. So we can use standard classloading and Spring to instantiate them.

Péter Török
+1  A: 

You haven't specified how the factory decides which implementation class to use, or whether the implementation classes need any custom initialization logic or parameters. Assuming simply instantiating the registered implementation class using the default constructor is sufficient, you can do:

interface Message<P, R> {
    void setParams(P Params); // and the corresponding getter

    void setResults(R results);
}

interface StringMessage extends Message<String, String> {
}

abstract class MessageImpl<P, R> implements Message<P, R> {
    P param;
    R results;
}

abstract class StringMessageImpl extends MessageImpl<String, String> implements
        StringMessage {

}
class ImplementationMap {
    private Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>();

    public <T> void register(Class<T> interfaceClass, Class<? extends T> implementationClass) {
        map.put(interfaceClass, implementationClass);
    }

    public <T> Class<? extends T> get(Class<T> interfaceClass) {
        return map.get(interfaceClass).asSubclass(interfaceClass);
    }
}

public class Test {
    static final ImplementationMap messageImplementors = new ImplementationMap() {{
        register(StringMessage.class, StringMessageImpl.class);
    }};

    static <P, R, M extends Message<P, R>> M create(Class<M> clazz, P p, R r)
            throws Exception {
        M m = messageImplementors.get(clazz).newInstance();
        m.setParams(p);
        m.setResults(r);
        return m;
    }

    public static void main(String[] args) throws Exception {
        StringMessage m = create(StringMessageImpl.class, "p", "r");
    }
}

PS: Are you sure you aren't overengineering? Do you really need a parallel type hierarchy for DTOs?

meriton
+1 for mentioning overengineering
Jeroen Rosenberg
I guess `M m = clazz.newInstance();` will not work as M is supposed to be an interface.
Arian
Is it? (OP writes "which would be given the message _class_")
meriton
Well, it should be the interface, as Arian pointed out, given to the factory and an implementing class should be returned ...
Joey
Assuming you mean _an object of_ the implementation class, I have updated my answer.
meriton
A: 

Given the interfaces interface Message<P,R> and interface MyMessage extends Message<String, String> and the class class BaseMessage<P,R> implements Message<P,R>, these are the options I see:

  1. Don't use MyMessage and replace all instances with Message<String, String>. Your factory will be

    public <P,R> Message<P, R> newMessage() { 
        return new BaseMessage<P,R>();
    }
    
  2. have a class

    class MyBaseMessage extends BaseMessage<String, String> implemens MyMessage
    

    The factory will be

    public <M extends Message> M newMessage(Class<M> m) { 
        if (m.isAssignableFrom(MyBaseMessage.class)) {
            return (M) new MyBaseMessage();
        }
        if (m.isAssignableFrom(BaseMessage.class)) {
            return (M) new BaseMessage();
        }
        return null;
    }
    
  3. Don't use interfaces.

  4. Don't use a factory.

  5. (if everything else breaks) Let your factory be

    public <M extends Message> M newMessage(Class<M> m) { 
        try {
            return getImplClass(m).newInstance();
        } catch (Exception ex) { /* do proper error handling */ }
    }
    

    getImplClass(): Use javassist to create an implementation class at runtime that implements the interface M and extends BaseMessage.

Arian