Как реализовать шаблон factory с дженериками в Java?

У меня есть общий интерфейс Обработчик

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

Я могу иметь n реализаций этого интерфейса. Предположим, что на данный момент у меня есть 2 реализации. Тот, который обрабатывает объекты String, а другой обрабатывает 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);
  }
}

Я хочу написать factory, который будет возвращать экземпляры обработчика на основе типа класса. Что-то вроде этого:

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

Я получаю следующую ошибку в этом factory:

Несоответствие типов: невозможно преобразовать из StringHandler в Handler<T>

Как это исправить?

Ответ 1

ПРОСТОЕ РЕШЕНИЕ

Вы можете сохранить ваши сопоставления Class<T> -> Handler<T> в Map. Что-то вроде:

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

В некотором месте инициализируйте обработчики (может быть в самом factory):

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

И в другом месте вы создаете и используете их:

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

БОЛЬШЕ КОМПЛЕКСНОГО РЕШЕНИЯ

Вы можете "сканировать" классы с помощью отражения и вместо того, чтобы вручную регистрировать сопоставления Class<T> -> Handler<T>, делать это с помощью отражения.

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

Затем вы создаете и используете их, как указано выше:

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

Чтобы реализовать getHandlerClasses(), просмотрите этот для сканирования всех классов в jar. Для каждого класса вы должны проверить, является ли это Handler:

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

Надеюсь, что это поможет!

Ответ 2

Ваша проблема в том, что компилятор не может сделать скачок в том, что правильный результат верен.

Чтобы помочь компилятору, вы можете сделать делегат factory делегировать конструкцию. Несмотря на то, что это выглядит странно и неудобно, ему удается надлежащим образом поддерживать безопасность типов без жертв, таких как литье или использование ? или сырых типов.

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

Ответ 3

Вы можете использовать что-то вроде:

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 является общим и компилятор не может отобразить его во время компиляции. Также безопаснее использовать .equals вместо ==.

Ответ 4

Весь смысл использования универсального типа заключается в совместном использовании реализации. Если реализация n интерфейса вашего обработчика настолько отличается, что они не могут быть разделены, то я не думаю, что есть какая-то причина использовать определение этого общего интерфейса в первую очередь. Вы бы предпочли, чтобы StringHandler и DateHandler были классами верхнего уровня.

С другой стороны, если реализация может быть разделена, как и в случае вашего примера, то factory работает естественным образом:

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

Ответ 5

как насчет этой реализации

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

Ответ 6

В основном вы можете:

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

Вывод:

TEST
Tue Dec 15 15:31:00 CET 2015

Вместо этого вместо написания двух разных методов для получения обработчиков отдельно всегда лучше.

Ответ 7

Я отредактировал ваш код и разрешил Eclipse "исправить" ошибки, и он придумал это.

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

    return null;
}

Ответ 8

Yout HandlerFactory не знает о T. Используйте ваш factory, как показано ниже -

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