2

I know there's many similar question but I had no luck finding a nice and clean solution if it's possible at all. I'm implementing a generic interface with subclasses of an abstract type. Problem is that when I'm calling them I either must do type cast in a switch/case or cast type in every method inside interface implementations and I can't figure out a nice and clean approach... I'll better just write down a short example.

// An abstract type with 2 implementations...
public abstract class ObjTypeAbstract {}

public class ObjType extends ObjTypeAbstract {}
public class ScriptType extends ObjTypeAbstract {}

Now the processor for both types with an interface

interface ProcessorInterface<T extends ObjTypeAbstract> {
    public void abcMethod(T obj);
}

public class ObjProcessor implements ProcessorInterface<ObjType> {
    public void abcMethod(ObjType obj) {}
}
public class ScriptProcessor implements ProcessorInterface<ScriptType> {
    public void abcMethod(ScriptType obj) {}
}

What I'm struggling with is a way of calling those processors based on ObjAbstractType. I have a single class that servers as middleware?? or how should I call it.:

Idea was to simple get the right processor via a single switch/case:

public class Processor {
    private ProcessorInterface objProcessor = new ObjProcessor();
    private ProcessorInterface scriptProcessor = new ScriptProcessor();

    public methodAbc(ObjAbstractType obj) {
        getProcessor(obj).abcMethod(obj);
    }

    private ProcessorInterface getProcessor(ObjAbstractType obj) {
        if (obj instanceof ObjType) {
            return objectProcessor;
        } else if (obj instanceof ScriptType) {
            return scriptProcessor;
        }

        return nullProcessor;
    }
}

This is what I'd like to have, it also takes care of type casting of objAbstract to actual type for abcMethod, problem is that it results in RawType warning which won't break the code, but I'd like to get rid of it.

And thats where I'm stuck... because if I cast processors to specific type like this:

private ProcessorInterface<ObjType> objProcessor = new ObjProcessor();
private ProcessorInterface<ScriptType> scriptProcessor = new ScriptProcessor();

I won't be able to return an abstract one from getProcessor method so I would have to implement those interfaces with an ObjAbstractType with all it's method and have type casting in all methods of every processor like:

public class ScriptProcessor implements ProcessorInterface<ObjAbstractType> {
    public void abcMethod(ObjAbstractType obj) {
        ScriptType scr = (ScriptType) obj;
    }
}

The other solution might be having a switch/case inside Processor middleware class and cast ObjAbstractType in it, but I'd have to write that switch inside abcMethod and all others or from getProcessor method returns both the Processor and casted ObjType... so I'd have to return some dto containing both. :/

Do you have any ideas / patterns that might help me to get rid of RawType call warning without extending the code with more switch/case or type casts? Wish you a nice day and I'll be glad for any discussion, David.

  • 1
    This code does have many mistakes. You missed some important things as the `extends` keyword for subclasses. What is `GmAbstractType` that you use but don't declare ? Please correct it if you want to make your question straight readable and helpful for others. – davidxxx Sep 09 '18 at 10:01
  • 1
    Right, I've updated the code example... those were just type mistakes as I've wrote down simplified version instead of actual code... Gm <=> Obj... and extends forgotten. :) – David Horáček Sep 09 '18 at 11:13
  • I supposed all of these but this is always better to have this cleared. Very good update. – davidxxx Sep 09 '18 at 11:46

2 Answers2

1

You need a way to store the mapping between a ObjTypeAbstract class and a ProcessorInterface instance.
You could use a Map that associates ObjTypeAbstracts (as key) to ProcessorInterfaces (as value).
About the raw type issue, you could use ProcessorInterface<? extends ObjTypeAbstract> for the declared variable but you will still need to perform a unsafe cast to ProcessorInterface<ObjTypeAbstract> to be able to invoke ProcessorInterface.abcMethod() with as parameter a ObjTypeAbstract declared type.
This cast is unavoidable with your actual design.

It could give something like :

public class Processor {

    private Map<Class<? extends ObjTypeAbstract>, ProcessorInterface<? extends ObjTypeAbstract >> map = new HashMap<>();

    public Processor(){
        map.put(ObjType.class, new ObjProcessor());
        map.put(ScriptType.class, new ScriptProcessor());   
    }
    public void methodAbc(ObjTypeAbstract obj) {
        @SuppressWarnings("unchecked")
        ProcessorInterface<ObjTypeAbstract> processorInterface = (ProcessorInterface<ObjTypeAbstract>) map.get(obj.getClass());
        processorInterface.abcMethod(obj);
    }

}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • Nice way. Especially I like the fact, that extending Processor with a new implementation now means a single line inside constructor. Thank you. – David Horáček Sep 09 '18 at 12:30
  • Ok I've finally got to try it and unfortunately with this it won't return a processor from the Map. I've implemented it in exactly same way yet '(ProcessorInterface) map.get(obj);' returns null. .. gonna look on it. – David Horáček Sep 09 '18 at 12:36
  • You are very welcome :) About your second comment, it is surprising. I just tested the compilation and it works. – davidxxx Sep 09 '18 at 13:11
  • 1
    Found it, it required to call 'map.get(obj.getClass());' ... I was missing that .getClass() part. – David Horáček Sep 09 '18 at 13:26
  • @David Horáček Oh sorry I didn't notice this. I just tested the compilation. You fast caught the issue, fine ! I updated to be correct. – davidxxx Sep 09 '18 at 13:29
0

I don't think there is a substantially more elegant way to get around some form of instanceof logic. However, there should not be need for casting, if you add some types to getProcessor.

    public <T extends ObjTypeAbstract> ProcessorInterface<T> getProcessor(Class<T> theClass) {
        if (theClass.isAssignableFrom(ObjType.class)) {
            return objProcessor;
        } else if (theClass.isAssignableFrom(ScriptType.class)) {
            return scriptProcessor;
        }
        return null;
    }

This can then be called like this:

    ProcessorInterface<ScriptType> scriptProcessor = new Processor().getProcessor(ScriptType.class);
    ProcessorInterface<ObjType> objProcessor = new Processor().getProcessor(ObjType.class);
Fritz Duchardt
  • 11,026
  • 4
  • 41
  • 60
  • This one looks interesting (never thought about such kind of declaration), but can't get the hang of it. As I'm returning objProcessor, which is type of ProcessorInterface it still gives an error that ProcessorInterface is not compatible with it. I'm gonna look deeper in it anyway. :) .. error is shown on return line inside getProcessor method. – David Horáček Sep 09 '18 at 12:01
  • Works on my machine ;) Also, the solution you flagged as correct uses a unchecked cast and does not protect against incorrect data setup: map.put(ObjType.class, new ScriptProcessor()); – Fritz Duchardt Sep 09 '18 at 14:21
  • @Fritz Duchardt Do you think that a series of conditional statement is necessarily a better solution ? About "does not protect against incorrect data setup", do you think that the conditional statements protect more against a typing error ? You could write `if (theClass.isAssignableFrom(ObjType.class)) { return scriptProcessor; }` and the compiler still compiles fine. At last, I don't understand how your method can compile without downcast. The `T` is a parameterized type scoped to the method. Any `ProcessorInterface` declared type you return needs to conform to `T`. – davidxxx Sep 09 '18 at 16:46
  • Where is the downcase in ProcessorInterface objProcessor = new Processor().getProcessor(ObjType.class)? – Fritz Duchardt Sep 09 '18 at 17:07
  • I referred the return statements inside `getProcessor()`. – davidxxx Sep 09 '18 at 17:27
  • The return type of getProcessor can be , which allows to return ScriptProcessor or ObjProcessor with a downcase. – Fritz Duchardt Sep 09 '18 at 17:34
  • That is what I said but you don't have any cast in your code. – davidxxx Sep 09 '18 at 17:43
  • Sorry, of course I mean to say "without a downcase" :)) Naturally, using bounded generic types there is no need for casting. – Fritz Duchardt Sep 09 '18 at 17:55