21

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?

Jordi Castilla
  • 26,609
  • 8
  • 70
  • 109
user1522820
  • 1,584
  • 5
  • 18
  • 31
  • 1
    Don't know why you want to go so deep into generics. Why not just have a getStringHandler() method and a getDateHandler() method and be done with it?..... its more clearer to the user of the Factory that way. – Oliver Watkins Dec 15 '15 at 14:34
  • 5
    You can't do what you're attempting in your example. The compiler can't follow your logic that if `clazz` is `String.class` then `T` is `String`. It's obvious to you, but not to the compiler. – Kayaman Dec 15 '15 at 14:40
  • @OliverWatkins Do you mean I should change Handler interface to take Object instead to type T and cast Object to respective types in implementing classes? But wouldn't that violate Liskov Substitution Principle? – user1522820 Dec 15 '15 at 14:46
  • no, the first two parts are perfectly fine. The third part, the factory is where I am not so sure about. Having a getStringHandler() method and a getDateHandler() method does not break Liskov Sub Principle at all. I think you are trying to be a bit too clever in the HandlerFactory... and its not really necessary having the getHandler metjhod the way you have it. – Oliver Watkins Dec 15 '15 at 14:50

8 Answers8

12

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!

Community
  • 1
  • 1
Héctor
  • 24,444
  • 35
  • 132
  • 243
  • As an addition to this answer, if you use Java 8, I would get rid of a separate factory class at all and place the static factory method in the Handler interface itself: this is logical and convenient. – yvolk Aug 08 '17 at 06:37
  • 1
    I know this is an old thread, and I came here because I had a similar problem. If you can use Spring 4, then you can use generic class type as a form of Qualifier: https://javawithravi.com/spring-generic-type-factory/ Hopefully this helps, or can work as documentation for posterity. – user770119 Sep 24 '18 at 02:09
  • 4
    Unless I'm missing something , the simple solution won't compile. The factory can't be generic as when you instantiate it there's no type you can provide in the first place. Thus, the following line doesn't compile: `Map, Handler> registry = new HashMap<>();` You can't use generic placeholders in a non-generic context – franDayz Apr 08 '20 at 08:04
  • All of the solutions here assume that we are directly factory on basis of class. While there might be more criterias other than class Type to return the handler. This is not a good soln. – Mukul Anand Aug 05 '21 at 21:05
6

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);
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • 2
    I have unchecked overriding on this place `Handler make() { return new StringHandler(); }` because expects ` Handler` but get `Handler` – Dred May 12 '22 at 10:32
3

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());
  }
}
user690421
  • 422
  • 1
  • 5
  • 15
2

You can use something like:

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

    return null;
  }
}

T is generic and the compiler can't map that at compile time. Also it is safer to use .equals instead of ==.

Albert Bos
  • 2,012
  • 1
  • 15
  • 26
1

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory method lets a class defer instantiation to subclasses.

Define generic abstract class

public abstract class Factory<T> {

public abstract T instantiate(Supplier<? extends T> supplier);

}

And a generic supplier

public class SupplierFactory<T> extends Factory<T> {

@Override
public T instantiate(Supplier<? extends T> supplier) {
    return supplier.get();
}
}

Then an implementation needs to have concrete classes to implement the base interface and a main class to show class defer instantiation . i.e

The base interface (desired interface of the requirement)

public interface BaseInterface {
void doAction();
}

The first concrete class

public class Alpha implements BaseInterface {
@Override
public void doAction() {
    System.out.println("The Alpha executed");
}
}

And the second one

public class Beta implements BaseInterface {
@Override
public void doAction() {
    System.out.println("The Beta executed");
}
}

The main

public class Main {
public static void main(String[] args) {
    Factory<BaseInterface> secondFactory = new SupplierFactory<>();
    secondFactory.instantiate(Beta::new).doAction();
    secondFactory.instantiate(Alpha::new).doAction();
}
}
Lunatic
  • 1,519
  • 8
  • 24
0

Yout HandlerFactory don't know about T. Use your factory like below-

public class HandlerFactory {
    public Handler<?> getHandler(Class<?> clazz) {
      if (clazz == String.class) {
          return new StringHandler();
      }
      if (clazz == Date.class) {
          return new DateHandler();
      }
      return null;
    }
}
kunsingh
  • 254
  • 2
  • 7
-2

Basically you can do:

 public Handler getHandler( Class clazz ){
        if( clazz == String.class ) return new StringHandler();
        if( clazz == Date.class ) return new DateHandler();

        return null;
    }

    public static void main( String[] args ){
        HandlerFactory handlerFactory = new HandlerFactory();
        StringHandler handler = ( StringHandler )handlerFactory.getHandler( String.class );
        handler.handle( "TEST" );
        DateHandler handler2 = ( DateHandler )handlerFactory.getHandler( Date.class );
        handler2.handle( new Date() );
    }

Output:

TEST
Tue Dec 15 15:31:00 CET 2015

But instead writing two different methods to get handlers separately always is a better way.

Sercan Ozdemir
  • 4,641
  • 3
  • 34
  • 64
-2

I edited your code and allowed Eclipse to "fix" the errors and it came up with this.

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

    return null;
}