Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement factory pattern with generics in Java?

I have a generic interface Handler

public interface Handler<T> {
  void handle(T obj);
}

I can have n implementations of this interface. Let's say I have following 2 implementations for now. One which handles String objects and another handles Date

public class StringHandler implements Handler<String> {
  @Override
  public void handle(String str) {
    System.out.println(str);
  }
}

public class DateHandler implements Handler<Date> {
  @Override
  public void handle(Date date) {
    System.out.println(date);
  }
}

I want to write a factory which will return handler instances based on the class type. Something like this :

class HandlerFactory {
  public <T> Handler<T> getHandler(Class<T> clazz) {
    if (clazz == String.class) return new StringHandler();
    if (clazz == Date.class) return new DateHandler();
  }
}

I get following error in this factory :

Type mismatch: cannot convert from StringHandler to Handler<T>

How to fix this?

like image 385
user1522820 Avatar asked Dec 15 '15 14:12

user1522820


People also ask

What is an example of the Factory Method Pattern used in Java?

Best Example of Factory method design pattern is valueOf() method which is there in String and wrapper classes like Integer and Boolean and used for type conversion i.e. from converting String to Integer or String to double in java..

What are generics in Java and how is it implemented?

Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.


3 Answers

SIMPLE SOLUTION

You could save your mappings Class<T> -> Handler<T> in a Map. Something like:

Map<Class<T>, Handler<T>> registry = new HashMap<>();

public void registerHandler(Class<T> dataType, Class<? extends Handler> handlerType) {
    registry.put(dataType, handlerType);
}

public <T> Handler<T> getHandler(Class<T> clazz) {
  return registry.get(clazz).newInstance();
}

In some place, initialize handlers (could be in the factory itself):

factory.registerHandler(String.class, StringHandler.class);
factory.registerHandler(Date.class, DateHandler.class);

And in another place, you create and use them:

Handler<String> stringhandler = factory.getHandler(String.class);
Handler<Date> dateHandler = factory.getHandler(Date.class);

MORE COMPLEX SOLUTION

You can "scan" classes using reflection and, instead of register manually the mappings Class<T> -> Handler<T>, do it using reflection.

for (Class<? extends Handler> handlerType : getHandlerClasses()) {
    Type[] implementedInterfaces = handlerType.getGenericInterfaces();
    ParameterizedType eventHandlerInterface = (ParameterizedType) implementedInterfaces[0];
    Type[] types = eventHandlerInterface.getActualTypeArguments();
    Class dataType = (Class) types[0]; // <--String or Date, in your case
    factory.registerHandler(dataType, handlerType);
}

Then, you create and use them like above:

Handler<String> stringhandler = factory.getHandler(String.class);
Handler<Date> dateHandler = factory.getHandler(Date.class);

To implement getHandlerClasses(), look at this to scan all classes in your jar. For each class, you have to check if it is a Handler:

if (Handler.class.isAssignableFrom(scanningClazz) //implements Handler
    && scanningClazz.getName() != Handler.class.getName()) //it is not Handler.class itself
{
        //is a handler!
}

Hope it helps!

like image 169
Héctor Avatar answered Oct 17 '22 02:10

Héctor


Your problem is that the compiler cannot make the leap to the fact thet the type of the result is correct.

To help the compiler you can make the factory delegate the construction. Although this looks strange and unwieldly it does manage to properly maintain type safety without sacrifices such as casting or using ? or raw types.

public interface Handler<T> {

    void handle(T obj);
}

public static class StringHandler implements Handler<String> {

    @Override
    public void handle(String str) {
        System.out.println(str);
    }
}

public static class DateHandler implements Handler<Date> {

    @Override
    public void handle(Date date) {
        System.out.println(date);
    }
}

static class HandlerFactory {

    enum ValidHandler {

        String {
                    @Override
                    Handler<String> make() {
                        return new StringHandler();
                    }
                },
        Date {
                    @Override
                    Handler<Date> make() {
                        return new DateHandler();
                    }
                };

        abstract <T> Handler<T> make();
    }

    public <T> Handler<T> getHandler(Class<T> clazz) {
        if (clazz == String.class) {
            return ValidHandler.String.make();
        }
        if (clazz == Date.class) {
            return ValidHandler.Date.make();
        }
        return null;
    }
}

public void test() {
    HandlerFactory factory = new HandlerFactory();
    Handler<String> stringHandler = factory.getHandler(String.class);
    Handler<Date> dateHandler = factory.getHandler(Date.class);
}
like image 38
OldCurmudgeon Avatar answered Oct 17 '22 02:10

OldCurmudgeon


The whole point of using a generic type is to share the implementation. If the n implementation of your Handler interface are so different that they can't be shared, then I don't think there is any reason to use define that generic interface at the first place. You'd rather just have StringHandler and DateHandler as top level classes.

On the other hand, if the implementation can be shared, as is the case of your example, then the factory works naturally:

public class Main {
    static public interface Handler<T> {
      void handle(T obj);
    }

    static public class PrintHandler<T> implements Handler<T> {
      @Override
      public void handle(T obj) {
        System.out.println(obj);
      }
    }

    static class HandlerFactory {
      public static <T> Handler<T> getHandler() {
        return new PrintHandler<T>();
      }
    }

    public static void main(String[] args) {
      Handler<String> stringHandler = HandlerFactory.getHandler();
      Handler<Date> dateHandler = HandlerFactory.getHandler();

      stringHandler.handle("TEST");
      dateHandler.handle(new Date());
  }
}
like image 2
user690421 Avatar answered Oct 17 '22 02:10

user690421