2017-06-04 104 views
1

OrderDBManager,UserDBManager,ProductDBManager擴展DBManager。 我正在嘗試創建一個單例DBManager Factory 以獲取不同DBManager類型類的實例。Singletons工廠使用泛型和反射

但我有一個問題,使用泛型返回確切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;            
    }}                 
+0

這是什麼'類的點。 forName(DB_MANAGER_PACKAGE_NAME + dbManagerClassName)'?調用者已經爲你提供了一個'Class'對象。 – Holger

回答

0

如果你需要得到明確的返回類型,那麼你可以使用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<>(); 
+0

好的。那麼地圖申報應該怎麼看? –

+0

我已經用可能的選項更新了答案 – udalmik

+0

獲得顯式返回類型的邏輯是可以的,只需要注意HashMap,它不同步。在生產環境中,應修改此代碼以避免破壞hashMap。 –

1

但我使用泛型返回的確切 DBManager型類用戶想要的實例有問題。

要返回方法中的特定類,應該指定範圍方法參數化類型,該類型允許類的客戶端指定方法應該返回的類型。

你可以這樣聲明方法:

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

爲了避免不安全的類型轉換,你可以通過使用Class#cast(Object)方法爲您執行從Map<String, DBManager>檢索到的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); 
+1

你不應該使用像'(T)...'這樣的未經檢查的轉換。相反,使用'dbManagerClass.cast(...)'。作爲附註,如果OP使用'dbManagerClass.newInstance()',則返回的引用已經是'T'類型。 OP的整個名稱處理看起來都是錯誤的... – Holger

+0

@Holger你是完全正確的。我會避免改變OP用來檢索類的方式:'class clazz = Class.forName(DB_MANAGER_PACKAGE_NAME + dbManagerClassName);'正如你注意到的那樣,它沒有被正確地使用。我只是修改了一個更好更全面的答案。 – davidxxx