How to implement factory template with generics in Java?

I have a common Handler interface

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

I can have n implementations of this interface. Suppose I currently have 2 implementations. One that processes String objects and another that processes 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 that will return handler instances based on the type of the class. Something like that:

 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 the following error in this factory:

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

How to fix it?

+11
source share
8 answers

A SIMPLE SOLUTION

You can save your Class<T> -> Handler<T> mappings Class<T> -> Handler<T> to Map . Sort of:

 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(); } 

Initialize the handlers in some place (maybe 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 INTEGRATED SOLUTION

You can β€œscan” classes using reflection and instead of manually registering the mappings Class<T> -> Handler<T> , do this 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 as described above:

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

To implement getHandlerClasses() , view this one to scan all classes in jar . For each class, you should 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 this helps!

+7
source

Your problem is that the compiler cannot make the jump in the correct result true.

To help the compiler, you can make the delegate factory delegate the construct. Although it looks weird and uncomfortable, does it manage to properly maintain type safety without sacrifices such as casting or use ? 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); } 
+2
source

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 cannot display it at compile time. It is also safer to use .equals instead of == .

0
source

The whole point of using a generic type is to share an implementation. If the implementation of the n interface of your handler is so different that they cannot be separated, then I don’t think there is any reason to use the definition of this common interface in the first place. You would prefer StringHandler and DateHandler to be top-level classes.

On the other hand, if the implementation can be split, as in 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()); } } 
0
source

how about this implementation

 public class HandlerFactory { public static <T>T createHandler(Class<T> interfaceTypes) throws IllegalAccessException, InstantiationException { return interfaceTypes.newInstance(); } public static void main(String[] args) throws InstantiationException, IllegalAccessException { DateHandler handler = createHandler(DateHandler.class); StringHandler stringHandler = createHandler(StringHandler.class); } } 
0
source

Basically you can:

  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 

Instead, instead of writing two different methods to get handlers separately, it is always better.

-1
source

I edited your code and allowed Eclipse to β€œfix” the errors, and he 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; } 
-1
source

Yout HandlerFactory doesn't know about T. Use your factory as shown 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; } } 
-1
source

Source: https://habr.com/ru/post/1238278/


All Articles