【问题标题】:Issue with type safety within abstract class抽象类中的类型安全问题
【发布时间】:2015-03-09 23:17:39
【问题描述】:

我最近开始使用 Java 开发一个新项目,该项目将有一个本地数据库。作为设计的一部分,我创建了一个 AbstractEntity 类 - 这旨在作为数据库中行(或潜在行)的对象表示。

虽然我在这个设计的早期遇到了一些问题,但我想确保我不会走上糟糕的道路。我遇到问题的一种特殊方法如下:

public ArrayList retrieveEntities(String sql)
{
    ArrayList ret = new ArrayList();

    String query = "SELECT " + getColumnsForSelectStatement() + " FROM " + getTableName() + " WHERE " + sql;

    try (Connection conn = DatabaseUtil.createDatabaseConnection();
      Statement s = conn.createStatement();
      ResultSet rs = s.executeQuery(query))
    {
        while (rs.next())
        {
            AbstractEntity entity = factoryFromResultSet(rs);
            ret.add(entity);
        }
    }
    catch (SQLException sqle)
    {
        Debug.logSqlException(query, sqle);
    }

    return ret;
}

这个方法背后的想法是有一个通用的方法从数据库中检索东西,我唯一需要传入的是 SQL 的条件。就目前而言,它可以正常工作,但我有两个问题:

1) 类型安全

我似乎无法在不导致编译器错误的情况下将此方法参数化。 ArrayList<AbstractEntity> 不好,我似乎也无法让 ArrayList<? extends AbstractEntity> 工作。当我尝试后者(这对我来说很有意义)时,以下行给了我一个编译器错误:

ArrayList<PlayerEntity> list = new PlayerEntity().retrieveEntities("1 = 1");

错误是“类型不匹配:无法从 ArrayList&lt;capture#1-of ? extends AbstractEntity&gt; 转换为 ArrayList&lt;PlayerEntity&gt;

有没有办法可以直接从抽象类中引用超类?这个方法不是静态的,因为你不能实例化一个抽象类(它没有构造函数),要调用它我必须总是有一个扩展类。那为什么我不能引用它的类型呢?

2) 静态

理想情况下,我希望此方法是静态的。这样我就可以直接调用 PlayerEntity.retrieveEntities() ,而不是仅仅创建一个对象来调用它。但是,由于它指的是抽象方法,所以我不能这样做,所以我坚持使用它。

以上两种抱怨都在我脑海中敲响了警钟。有没有更好的设计方法来避免这些问题,或者有更好的直接解决方案来解决我遗漏的这些问题?

【问题讨论】:

标签: java generics static abstract


【解决方案1】:

我认为您正在重新发明轮子。 ORMs (Object-Relational Mappers) 已经存在很多年了,并且被证明非常有用。

不过,它们不是防弹的。由于他们打算解决的问题非常困难(我的意思是object-relational impedance mismatch),因此解决方案通常也有其困难。

为了以灵活的方式完成他们的工作,一些 ORM 会牺牲性能,而另一些会牺牲使用的简单性等。我的意思是,这里没有完美的解决方案。

我想向您指出我在不同项目中使用过的三个不同的 ORM:

这里有许多比较和基准,可以深入讨论这个主题。

Hibernate 是使用最广泛的,它健壮而强大,提供了很大的灵活性并且性能良好如果使用得当。不利的是,它有一个陡峭的学习曲线,对于初学者来说有点复杂,而且一般来说,使用 Hibernate 的解决方案最终会永远使用 Hibernate,因为很容易无意中让 Hibernate 潜入您的业务层。

ActiveJDBC 不是很流行,但它是 Java 最好的 ActiveRecord 解决方案。如果你来自 Ruby,这是你的选择。它的 API 非常流畅和富有表现力,使用它的代码非常易于阅读和维护。它非常简单,而且是一个非常的框架。

E-Bean 非常强大,它的 API 流畅且富有表现力,并且该产品提供了自适应行为来优化动态查询。它使用简单,使用它的代码可读性好,易于维护。

关于类型安全,我通常采用这种方法:

public class AbstractRepository<T extends AbstractEntity> {

    protected final Class<T> entityClazz;

    protected AbstractRepository() {
        Type type = this.getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        this.entityClazz = (Class<T>) paramType.getActualTypeArguments[0];
        // TODO exception handling
    }

    public List<T> list(String sql) { // retrieveEntities => very long name
        List<T> ret = new ArrayList<>();

        String query = "SELECT " + getColumnsForSelectStatement() + " FROM " + getTableName() + " WHERE " + sql;

        try (Connection conn = DatabaseUtil.createDatabaseConnection();
            Statement s = conn.createStatement();
            ResultSet rs = s.executeQuery(query)) {
            while (rs.next()) {
                T entity = factoryFromResultSet(rs);
                ret.add(entity);
            }
        } catch (SQLException sqle) {
            Debug.logSqlException(query, sqle);
        }

        return ret;
    }

    protected T factoryFromResultSet(ResultSet rs) {
        // Create new entity instance by reflection
        T entity = clazz.getConstructor().newInstance();

        // TODO exception handling 
        // Fill entity with result set data

        return entity;
    }
}

我已经声明了一个抽象存储库类,它需要使用正确的参数类型进行扩展:

public class Person extends AbstractEntity {
}

public class PersonRepository extends AbstractRepository<Person> {
}

PersonRepository repo = new PersonRepository();
List<Person> people = repo.list("some SQL");

我通常将生成实体的代码与实际实体的代码分开,否则实体最终会承担很多责任并做太多工作。但是,ActiveRecord 方法通过让实体完成所有工作来解决这个问题,它是 Java 世界之外非常流行的选择。

【讨论】:

    【解决方案2】:

    第一点:如果你的方法是静态的,那么使用多态是没有意义的。多态性在运行时有效,但是将方法设为静态会强制您在编译时指示动态类型,这是无稽之谈。

    关于方法的返回类型,您可能需要先将其设置为ArrayList&lt;? extends AbstractEntity&gt;,否则您只是说您返回任何对象(即ArrayList&lt;Object&gt;)。 在此之后,您必须创建一个与返回类型相同类型的局部变量,以免出现编译错误。

    现在,您如何填充此集合? 我只给你两个提示:

    1. 您可以使用Reflection,特别是您可以通过在运行时选择要实例化的类来调用类构造函数(使用getClass())。
    2. 您可以利用Template method pattern 最终获得灵活的设计并且没有重复代码。

    不过,总的来说,您所面临的问题已经解决了。所以,如果你只是在寻找一个现成的解决方案,你可以看看像 Hibernate 这样的 ORM 框架。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多