【问题标题】:Singletons factory using generics and reflection使用泛型和反射的单例工厂
【发布时间】:2017-06-04 13:42:19
【问题描述】:

OrderDBManager、UserDBManager、ProductDBManager 扩展 DBManager。 我正在尝试创建一个单例 DBManager 工厂 获取不同 DBManager 类型类的实例。

但是我在使用泛型返回用户想要的确切 DBManager 类型类的实例时遇到问题。现在它只适用于 DBManager 本身。 非常感谢您的帮助!

public class DBManagerFactory {

     private static final Map<String, DBManager> instances = new HashMap<>();

     private static final String DB_MANAGER_PACKAGE_NAME = "ua.Test.db.";

     public static DBManager getInstance(Class<? extends DBManager> dbManagerClass) {
            String dbManagerClassName = dbManagerClass.getSimpleName();
            DBManager dbManager = instances.get(dbManagerClassName);

            if(dbManager == null){
                Class clazz = Class.forName(DB_MANAGER_PACKAGE_NAME + dbManagerClassName);
                dbManager = (DBManager)clazz.newInstance();
                instances.put(dbManagerClassName, dbManager);    
            }
            return dbManager;                                             
     }}                                                                

【问题讨论】:

  • Class.forName(DB_MANAGER_PACKAGE_NAME + dbManagerClassName) 的意义何在?调用者已经向您提供了一个Class 对象。

标签: java generics reflection singleton factory


【解决方案1】:

但是我在使用泛型返回精确的实例时遇到问题 用户想要的 DBManager 类型类。

要在方法中返回特定类,您应该指定一个作用域方法参数化类型,该类型允许该类的客户端指定该方法应返回的类型。

你可以这样声明方法:

public static <T extends DBManager> T getInstance(Class<T> dbManagerClass)  {

为避免不安全的转换,您可以使用 Class#cast(Object ) 方法将从 Map&lt;String, DBManager&gt; 检索到的 DBManager 实例转换为 T extends DBManager 类型,该方法在转换 DBManager 实例之前为您执行类型检查:

T dbManager = dbManagerClass.cast(instances.get(dbManagerClassName));

最后,检索类的方法:

Class clazz = Class.forName(DB_MANAGER_PACKAGE_NAME + dbManagerClassName);

不是必需的,因为您已经将类作为方法的参数。
直接用就行了。

这是实现此解决方案的示例代码:

public class DBManagerFactory {

    private static final Map<String, DBManager> instances = new HashMap<>();

    public static <T extends DBManager> T getInstance(Class<T> dbManagerClass)  {

      final String dbManagerClassName = dbManagerClass.getSimpleName();

      T dbManager = dbManagerClass.cast(instances.get(dbManagerClassName));

      if (dbManager == null) {
        try {
          dbManager =  dbManagerClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
          e.printStackTrace(); // or better log it
          throw new RuntimeException("faillure during reflection instantiation for class " + dbManagerClass.getName(), e);
        }
        instances.put(dbManagerClassName, dbManager);
      }

      return dbManager;
    }   

}

通过这些修改,客户端类可以以这种方式调用该方法:

DBManager instance = DBManagerFactory.getInstance(DBManager.class);
DBManagerChild child = DBManagerFactory.getInstance(DBManagerChild.class);

【讨论】:

  • 你不应该使用像(T) …这样的未经检查的演员表。相反,请使用dbManagerClass.cast( … )。附带说明一下,如果 OP 使用 dbManagerClass.newInstance(),则返回的引用已经具有 T 类型。无论如何,OP 对名称的整个处理看起来都是错误的……
  • @Holger 你是完全正确的。我会避免更改 OP 用于检索类的方式:Class clazz = Class.forName(DB_MANAGER_PACKAGE_NAME + dbManagerClassName); 正如您所注意到的,它没有正确使用。我只是为了给出更好更全面的答案而进行了修改。
【解决方案2】:

如果需要明确返回类型,可以使用generic factory method,如下:

class Base {}

class A extends Base {}

class B extends Base {}

class InitializationFailedException extends RuntimeException {
    public InitializationFailedException(Throwable cause) {
        super(cause);
    }
}

class Factory {

    private static Logger log = LoggerFactory.getLogger(Factory.class);

    private static final ConcurrentHashMap<Class<? extends Base>, Base> instances = new ConcurrentHashMap<>();

    public static  <T extends Base> T getInstance(Class<T> tClass) {
        if (tClass == null) {
            throw new NullPointerException();
        }
        return (T) instances.computeIfAbsent(tClass, (tClass1) -> {
            try {
                return tClass1.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("Unable to create singleton value for " + tClass1.getCanonicalName(), e);
                throw new InitializationFailedException(e);
            }
        });
    }

}

通过这种方法,您可以创建子类型引用:

A aInstance = Factory.getInstance(A.class);
B bInstance = Factory.getInstance(B.class);

对于您的问题,方法签名可能如下所示:

public static <T extends DBManager> T getInstance(Class<T> dbManagerClass);

实例映射应如下所示:

private static final Map<Class<? extends DBManager>, DBManager> instances = new HashMap<>();

【讨论】:

  • 好的。那么地图声明应该怎么看呢?
  • 获取显式返回类型的逻辑是可以的,只需要注意不同步的HashMap。在生产环境中,应修改此代码以避免破坏 hashMap。
  • @LuigiRubino 问题是关于泛型,而不是关于线程安全或任何其他重要但单独主题
  • throws Exception?有一个反模式。
  • @LewBloch 你是对的。但是,我个人不喜欢答案包含大量“生产就绪”不必要的代码,这会带来更多的混乱而不是价值。无论如何,修改答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多