2

我正在尝试在 Java 中实现一个接口,以便为一个应用程序使用不同类型的数据库。我的想法是创建一个具有公共接口和两个静态变量的抽象类,然后被子类覆盖。然后我想添加一个 Class[] 列表,其中包含所有可用子类的类到抽象类以及允许确定要使用的正确类的几个函数。

目标是首先获取所有可用数据库类型的列表并让用户选择一个。之后,另一个函数应该将名称(可以本地化)转换为IDENTIFIER子类中指定的名称。最后,第三个函数允许通过给出这样的IDENTIFIER.

我的抽象类看起来像这样:

public abstract class DataBase {
    public static final IDENTIFIER = "";
    public static final NAME = "";
    private static final Class[] dbTypes = new Class[]{PostgreSQL.class, MySQL.class};

    public static String[] getNameList() {
        String[] names = new String[dbTypes.length];
        for(int i = 0; i < dbTypes.length; i++){
            names[i] = dbTypes[i].NAME;       //Cannot access the static variable this way.
        }
        return names;
    }
    public static String getIdentifierForName(String name) {
        for(int i = 0; i < dbTypes.length; i++){
            if(name.equals(dbTypes[i].NAME){       
                return dbTypes[i].IDENTIFIER;
            }
        }
        return "";
    }
    public static DataBase getInstanceOf(String identifier) {
        for(int i = 0; i < dbTypes.length; i++){
            if(identifier.equals(dbTypes[i].IDENTIFIER) {       
                return dbTypes[i].newInstance();
            }
        }
        return null;
    }
}

Child 类看起来像这样:

public class MySQL extends DataBase {
    public static final IDENTIFIER = "ab.cde.MySQL";
    public static final NAME = "MySQL";
    ...
}
public class PostgreSQL extends DataBase{
    public static final IDENTIFIER = "ab.cde.PostgreSQL";
    public static final NAME = "PostgreSQL";
    ...
}

我现在的问题是,我无法从 Class 对象访问静态变量。显然 dbTypes 列表不包含任何类型化的类。我尝试将 Array 的类型更改为Class<? extends DataBase>,但出现错误Cannot create a generic array of Class<? extends DataBase>我还尝试使用检查类isAssignableFrom()然后强制转换类,但我仍然无法访问静态变量。

现在我有两个有效的解决方案:

  1. 将所有现有的子类硬编码到每个函数if(PostgreSQL.NAME.equals(name)){...}等中。但是,如果我添加新的子类,我只想在我的实现中添加它们。

  2. 除了使用 Class[] 数组,我可以使用 DataBase[] 数组和每个类的实例。但是,我认为实例化每个可用的 DataBase 子类是一种不好的做法,即使我最终只需要一个。

因为我之前从未做过这样的事情,所以我也可能完全错误地处理这个问题。也许我错过了通常完成此类事情的正确方法?

感谢您的帮助。

4

4 回答 4

2

Java 中没有“抽象属性”。您必须在DataBase类中创建两个抽象方法,如下所示:

public abstract class DataBase {

    // No "abstract propeties"

    public abstract String getDBName();
    public abstract String getDBIdentifier();

    // etc etc...

}

然后,在每个子类中:

public class MySQL extends DataBase {

    public static final IDENTIFIER = "ab.cde.MySQL";
    public static final NAME = "MySQL";

    @Override
    public String getDBName() {
       return NAME;
    }

    @Override
    public String getDBIdentifier() {
       return IDENTIFIER;
    }

    // etc etc...

}

使用类时,您只需转换为DataBase(not MySQLor PostgreSQL) 并调用两个抽象方法。

因此,为了解决您的“选择数据库类”问题,我将创建一个包含数据库名称和相应类的配置文件,并根据需要使用反射(newInstance())对其进行实例化。

作为替代方案,您可以使用反射来访问静态变量,如 Nikita 建议的答案,或者您可以只使用类的名称作为它支持的数据库的标识符,如下所示(未测试):

public abstract class DataBase {

    private static final Class[] dbTypes = new Class[]{PostgreSQL.class, MySQL.class};

    public static Class getDBClass(String type) {
       for (Class c : dbTypes) {
           if (c.getSimpleName().toLowerCase().equals(type.toLowerCase())) {
               return c;
           }
       }
       return null;
    }

    public static Set<String> getSupportedDB() { // <-- you populate a dropdown menu with this
       Set<String> supported = new HashSet<String>();
       for (Class c : dbTypes) {
           supported.add(c.getSimpleName());
       }
       return supported;
    }

    // etc etc...

}

但是,我不喜欢这个解决方案,也不会使用它。

于 2013-02-19T09:30:01.087 回答
0

您可以使用反射来获取每个类的值:

public static String[] getNameList(){
    String[] names = new String[dbTypes.length];
    for(int i=0; i<dbTypes.length; i++){
        Field f = dbTypes[i].getField("NAME");
        names[i] = f.get(null);
    }
    return names;
}

但它可能会很慢。

我还建议创建单独的枚举DBRegistry,其中包含名称、标识符和类:

public enum DBRegistry {
   MYSQL("ab.cde.MySQL", "MySQL", MySQL.class),
   POSTGRESQL("ab.cde.PostgreSQL", "PostgreSQL", PostgreSQL.class);

   private String name;
   private String identifier;
   private Class<?> dbClass;

   private DBRegistry(String identifier, String name, Class<?> dbClass) {
       this.identifier = identifier;
       this.name = name;
       this.dbClass = dbClass;
   }
   // Getters...
}

您可以使用迭代注册表中的所有项目DBRegistry.values

于 2013-02-19T09:35:30.420 回答
0

未经测试,但我会建议这样的事情。您可以通过调用 DataBase.registerDataBase(new DataBase(...))); 来注册数据库。可以从主文件调用。

    public class DataBase {
          private final static List<DataBase> INSTANCES = new ArrayList<DataBase>();
          private final String identifier;
          private final String name;
          private final Class<?> dbType;
          public DataBase(String identifier, String name, Class<?> dbType) {
           this.identifier=identifier.toString();
           this.name=name.toString();
           this.dbType=dbType;         
          }

          String getIdentifier() {return identifier;}
          String getName()       {return identifier;}
          Class<?> getDbType()   {return dbtype;}

          public synchronized static void registerDatabase(DataBase database) {
            database.getClass();
            INSTANCES.add(database);
            //may check if already registered and either fail or replace it
          }

          public synchronized static List<DataBase> getNameList() {
            return new ArrayList<DataBase>(INSTANCES);
          }

          public synchronized static List<String> getNameList() {
            List<String> names = new ArrayList<String>(INSTANCES.size());
            for (Database db:INSTANCES) names.add(db.getName());
            return names;
          }

          public synchronized static String getIdentifierForName(String name) {       
                for(DataBase db:INSTANCES){
                    if(name.equals(db.getName())) return db;
                }           
            return null;
          }

          public synchronized static DataBase getInstanceOf(String identifier) {
            for(DataBase db:INSTANCES){
                    if(identifier.equals(db.getIdentifier())) return db;
                }           
            return null;
          }
        }
}
于 2013-02-19T10:13:12.800 回答
0

我建议保持简单,不要超过在实际应用程序中使用的必要条件。扩展事物比重构代码以适应额外的复杂性更容易。您提到的大多数内容只是您解决问题的人工制品,而不是您的应用程序本身的实际需求。碰巧的是,现代的面向对象语言拥有你需要的一切,你可以实现一个好的设计而无需反射,也无需求助于静态属性和字符串标识符。

记住要依赖编译器而不是运行时来处理您事先知道的任何事情 - 任何已知不会从一个应用程序运行到另一个应用程序的东西都不需要反射,因为它不涉及运行时变量!我会选择接口、实现它们的类,更重要的是使用工厂模式来抽象使用这些类:

interface Database
{
    void query(String sqlString);
}

class MySQLDatabase implements Database
{
    public void query(String sqlString)
    {

    }
}

class PostgreSQLDatabase implements Database
{
    public void query(String sqlString)
    {

    }
}

class DatabaseFactory
{
    Database connectDatabase()
    {
        /// F.e. return new MySQLDatabase();
    }
}

The whole "database abstraction layer" has been done to death already anyway, giving birth to DBA, ODBC and other software stacks that solve your problem. You should let yourself be inspired by these, unless you are sure your particular way of solving this yields advantages that can be proven. If you want to go about this in a professional way, of course. If you want to educate yourself, by all means, use reflection, strings in place of more specific objects, and tight-coupling instead of aggressive modularity.

于 2013-02-19T17:36:02.120 回答