Как реализовать шаблон 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;
}
}