jeudi 21 janvier 2016

Java Factory using Generics, .class vs. .getClass()

I searched and tried now for more than a day and could not find a solution for a common problem I have in Java. The reason is obvious - Type erasure. But the question I have is: Is there really no good solution for this problem in Java? I am willing to investigate even more time since this kind of problem pops up every once in a time.

The error I get is:

The method doStrategy(capture#2-of ? extends I) in the type IStrategy is not applicable for the arguments (I)

So I simplified the problem to the following example.

Imagine the model:

package model;

public interface I {

//there are actual 30 classes implementing I...
}

public class A implements I {

    public void someSpecificMagicForA(){
        System.out.println("A");
    }
}

public class B implements I {

    public void someSpecificMagicForB() {
        System.out.println("B");
    }

}

and the selection logic

package strategy;

import model.A;

public interface IStrategy<T> {

    public void doStrategy(T t);
}

public class AStrategy implements IStrategy<A> {

    @Override
    public void doStrategy(A a) {
        a.someSpecificMagicForA();
    }
}

public class BStrategy implements IStrategy<B> {

    @Override
    public void doStrategy(B b) {
        b.someSpecificMagicForB();
    }
}

and a generic strategy factory

package strategy;

import java.util.HashMap;
import java.util.Map;

import model.A;
import model.B;

public class StrategyFactory {

    static {
        strategies.put(A.class, AStrategy.class);
        strategies.put(B.class, BStrategy.class);
    }
    private static final Map<Class<?>, Class<? extends IStrategy<?>>> strategies = new HashMap<>();

    @SuppressWarnings("unchecked") // I am fine with that suppress warning
    public <T> IStrategy<T> createStategy(Class<T> clazz){
        Class<? extends IStrategy<?>> strategyClass = strategies.get(clazz);

        assert(strategyClass != null);

        try {
            return (IStrategy<T>) strategyClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
}

And here is the test

import java.util.ArrayList;
import java.util.List;

import junit.framework.TestCase;
import model.A;
import model.B;
import model.I;
import strategy.IStrategy;
import strategy.StrategyFactory;


public class TestCases extends TestCase {

    public void testWithConcreteType(){

        B b = new B();

        StrategyFactory factory = new StrategyFactory();
        IStrategy<B> createStategy = factory.createStategy(B.class);
        createStategy.doStrategy(b); //awesome
    }

    public void testWithGenericType(){

        List<I> instances = createTestData(); // image this is the business data

        StrategyFactory factory = new StrategyFactory();

        for (I current : instances){
            IStrategy<? extends I> createStategy = factory.createStategy(current.getClass());
            createStategy.doStrategy(current); //meh
            //The method doStrategy(capture#2-of ? extends I) in the type IStrategy<capture#2-of ? extends I> 
            //is not applicable for the arguments (I)
        }
    }

    private List<I> createTestData(){
        A a = new A();
        B b = new B();

        List<I> instances = new ArrayList<>();
        instances.add(a);
        instances.add(b);

        return instances;
    }
}

I have tried another approach using guava TypeTokens (http://ift.tt/1iFUmjw). But I did not manage to get this working since I really have no since all I get is that Collection of instances implementing that interface.

I have a working and not so bad solution though using the Visitor-pattern. Since I there have the real classes

...visitor class
public void visit(A a){
   doVisit(A.class, a); //private generic method now works of course
}

everything is fine again at compile time. But in this particular case it took me quite some time to implement that visitor for more than 30 sub-classes of I. So I would really like to have a better solution for the future.

Any comments are much appreciated.





Aucun commentaire:

Enregistrer un commentaire