【问题标题】:How to implement factory pattern with generics in Java?如何在 Java 中使用泛型实现工厂模式?
【发布时间】:2016-03-21 09:22:29
【问题描述】:

我有一个通用接口Handler

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

我可以有这个接口的 n 个实现。假设我现在有以下 2 个实现。一个处理字符串对象,另一个处理日期

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

我想编写一个工厂,它将根据类类型返回处理程序实例。像这样的:

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

我在这个工厂遇到以下错误:

类型不匹配:无法从 StringHandler 转换为 Handler&lt;T&gt;

如何解决这个问题?

【问题讨论】:

  • 不知道为什么要深入研究泛型。为什么不只拥有一个 getStringHandler() 方法和一个 getDateHandler() 方法并完成它?.....这样对 Factory 的用户来说更清楚。
  • 你不能做你在你的例子中尝试的事情。编译器无法遵循您的逻辑,即如果 clazzString.classTString。这对你来说很明显,但对编译器来说却不是。
  • @OliverWatkins 你的意思是我应该改变 Handler 接口以使用 Object 而不是类型 T 并将 Object 转换为实现类中的相应类型?但这不会违反里氏替换原则吗?
  • 不,前两部分非常好。第三部分,工厂是我不太确定的地方。拥有一个 getStringHandler() 方法和一个 getDateHandler() 方法根本不会破坏 Liskov 子原则。我认为你试图在 HandlerFactory 中变得有点太聪明了......而且你没有必要以你的方式拥有 getHandler 方法。

标签: java generics factory


【解决方案1】:

简单的解决方案

您可以将映射 Class&lt;T&gt; -&gt; Handler&lt;T&gt; 保存在 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.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&lt;T&gt; -&gt; Handler&lt;T&gt;,而是使用反射来完成。

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(),请查看this 以扫描jar 中的所有类。对于每个类,您必须检查它是否是Handler

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

希望对你有帮助!

【讨论】:

  • 作为这个答案的补充,如果您使用 Java 8,我将完全摆脱一个单独的工厂类,并将静态工厂方法放在 Handler 接口本身:这是合乎逻辑且方便的。
  • 我知道这是一个旧线程,我来到这里是因为我遇到了类似的问题。如果您可以使用 Spring 4,那么您可以使用泛型类类型作为限定符的一种形式:javawithravi.com/spring-generic-type-factory 希望这会有所帮助,或者可以作为后代的文档。
  • 除非我遗漏了什么,否则简单的解决方案将无法编译。工厂不能是通用的,因为当您实例化它时,您一开始就无法提供任何类型。因此,以下行无法编译:Map&lt;Class&lt;T&gt;, Handler&lt;T&gt;&gt; registry = new HashMap&lt;&gt;(); You can't use generic placeholders in a non-generic context
  • 这里所有的解决方案都假设我们是直接基于类的工厂。虽然除了类 Type 之外可能还有更多标准来返回处理程序。这不是一个好的解决方案。
【解决方案2】:

您的问题是编译器无法跳到结果类型正确的事实。

为了帮助编译器,您可以让工厂委托构造。尽管这看起来很奇怪且笨拙,但它确实能够正确地维护类型安全,而无需牺牲诸如强制转换或使用 ? 或原始类型。

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】:

    使用泛型类型的全部意义在于共享实现。如果您的 Handler 接口的 n 实现如此不同以至于无法共享,那么我认为没有任何理由首先使用定义该通用接口。您宁愿将 StringHandler 和 DateHandler 作为顶级类。

    另一方面,如果实现可以共享,就像您的示例一样,那么工厂自然会工作:

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

    【讨论】:

      【解决方案4】:

      你可以使用类似的东西:

      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 而不是== 更安全。

      【讨论】:

        【解决方案5】:

        基本上你可以这样做:

         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
        

        但是编写两种不同的方法来分别获取处理程序总是更好的方法。

        【讨论】:

          【解决方案6】:

          我编辑了你的代码并允许 Eclipse “修复”错误,它想出了这个。

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

          【讨论】:

            【解决方案7】:

            你的 HandlerFactory 不知道 T。像下面这样使用你的工厂-

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-04-01
              • 1970-01-01
              • 1970-01-01
              • 2021-11-19
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多