13

我正在为我正在编写的 Java 程序制作一个迷你 ORM……我的数据库中的每个表都有一个类,所有表都继承自ModelBase.

ModelBase是抽象的,提供了一堆静态方法来从数据库中查找和绑定对象,例如:

public static ArrayList findAll(Class cast_to_class) {
  //build the sql query & execute it 
}

所以你可以做一些事情,比如ModelBase.findAll(Albums.class)获取所有持久专辑的列表。我的问题是,在这个静态上下文中,我需要从具体类 Album 中获取适当的 sql 字符串。我不能有像这样的静态方法

public class Album extends ModelBase {
  public static String getSelectSQL() { return "select * from albums.....";}
}

因为Java中没有静态方法的多态性。但是我不想在其中创建getSelectSQL()一个实例方法,Album因为我需要创建它的一个实例只是为了获得一个行为上真正静态的字符串。

目前,findAll()使用反射来为有问题的类获取适当的 sql:

select_sql = (String)cast_to_class.getDeclaredMethod("getSelectSql", new Class[]{} ).invoke(null, null);

但这太恶心了。

那么有什么想法吗?这是我一次又一次遇到的一个普遍问题——无法在类或接口中指定抽象静态方法。我知道为什么静态方法多态性不能也不能工作,但这并不能阻止我想再次使用它!

是否有任何模式/结构可以让我确保具体的子类 X 和 Y 实现一个类方法(或者如果失败,一个类常量!)?

4

9 回答 9

4

在这里使用静态是错误的。

概念上的静态是错误的,因为它仅适用于与实际对象(物理或概念)不对应的服务。您有许多表,每个表都应该由系统中的实际对象表示,而不仅仅是一个类。这听起来有点理论,但它有实际的后果,正如我们将看到的那样。

每个表属于不同的类别,这没关系。由于您只能拥有一个表,因此将每个类的实例数限制为一个(使用标志 - 不要使其成为单例)。让程序在访问表之前创建一个类的实例。

现在你有几个优点。由于您的方法不再是静态的,因此您可以使用继承和覆盖的全部功能。您可以使用构造函数进行任何初始化,包括将 SQL 与表相关联(您的方法稍后可以使用的 SQL)。这应该会让你上面的所有问题都消失,或者至少变得更简单。

似乎必须创建对象和额外的内存需要额外的工作,但与优势相比,它真的微不足道。对象的几个字节内存不会被注意到,少数构造函数调用可能需要十分钟才能添加。这样做的好处是,如果不使用表,则不需要运行初始化任何表的代码(不应调用构造函数)。你会发现它简化了很多事情。

于 2008-09-30T14:01:35.947 回答
2

虽然,我完全同意“在这里使用静态是错误的东西”这一点,我有点理解你在这里想要解决的问题。仍然实例行为应该是工作方式,但如果你坚持这是我会做的:

从您的评论开始“我需要创建它的一个实例只是为了获得一个行为上真正静态的字符串”

这并不完全正确。如果你看起来不错,你并没有改变基类的行为,只是改变了方法的参数。换句话说,您正在更改数据,而不是算法。

当一个新的子类想要改变一个方法的工作方式时,继承更有用,如果你只需要改变这个类用来工作的“数据”,那么这样的方法就可以了。

class ModelBase {
    // Initialize the queries
    private static Map<String,String> selectMap = new HashMap<String,String>(); static {
        selectMap.put( "Album", "select field_1, field_2 from album");
        selectMap.put( "Artist", "select field_1, field_2 from artist");
        selectMap.put( "Track", "select field_1, field_2 from track");
    }

    // Finds all the objects for the specified class...
    // Note: it is better to use "List" rather than "ArrayList" I'll explain this later.
    public static List findAll(Class classToFind ) {
        String sql = getSelectSQL( classToFind );
        results = execute( sql );
        //etc...
        return ....
    }

    // Return the correct select sql..
    private static String getSelectSQL( Class classToFind ){
        String statement = tableMap.get( classToFind.getSimpleName() );
        if( statement == null ) {
            throw new IllegalArgumentException("Class " + 
                 classToFind.getSimpleName + " is not mapped");
        }
        return statement;

    }
}

也就是说,用一个 Map 映射所有语句。“显而易见”的下一步是从外部资源(例如属性文件、xml 甚至(为什么不)数据库表)加载地图,以获得额外的灵活性。

这样你就可以让你的班级客户(和你自己)开心,因为你不需要“创建一个实例”来完成这项工作。

// Client usage:

...
List albums = ModelBase.findAll( Album.class );

...

另一种方法是从后面创建实例,并在使用实例方法时保持客户端接口完整,这些方法被标记为“受保护”以避免外部调用。以与上一个示例类似的方式,您也可以执行此操作

// Second option, instance used under the hood.
class ModelBase {
    // Initialize the queries
    private static Map<String,ModelBase> daoMap = new HashMap<String,ModelBase>(); static {
        selectMap.put( "Album", new AlbumModel() );
        selectMap.put( "Artist", new ArtistModel());
        selectMap.put( "Track", new TrackModel());
    }

    // Finds all the objects for the specified class...
    // Note: it is better to use "List" rather than "ArrayList" I'll explain this later.
    public static List findAll(Class classToFind ) {
        String sql = getSelectSQL( classToFind );
        results = execute( sql );
        //etc...
        return ....
    }

    // Return the correct select sql..
    private static String getSelectSQL( Class classToFind ){
        ModelBase dao = tableMap.get( classToFind.getSimpleName() );
        if( statement == null ) {
            throw new IllegalArgumentException("Class " + 
                 classToFind.getSimpleName + " is not mapped");
        }
        return dao.selectSql();
    }
    // Instance class to be overrided... 
    // this is "protected" ... 
    protected abstract String selectSql();
}
class AlbumModel  extends ModelBase {
    public String selectSql(){
        return "select ... from album";
    }
}
class ArtistModel  extends ModelBase {
    public String selectSql(){
        return "select ... from artist";
    }
}
class TrackModel  extends ModelBase {
    public String selectSql(){
        return "select ... from track";
    }
}

而且你不需要改变客户端代码,仍然拥有多态的力量。

// Client usage:

...
List albums = ModelBase.findAll( Album.class ); // Does not know , behind the scenes you use instances.

...

我希望这有帮助。

关于使用 List 与 ArrayList 的最后一点说明。对接口编程总是比对实现更好,这样你的代码就更灵活了。您可以使用另一个更快的 List 实现,或执行其他操作,而无需更改客户端代码。

于 2008-09-30T17:17:25.320 回答
1

为什么不使用注释?它们非常适合您正在做的事情:将元信息(这里是 SQL 查询)添加到类中。

于 2008-09-30T04:49:07.817 回答
1

正如建议的那样,您可以使用注释,或者您可以将静态方法移动到工厂对象:

public abstract class BaseFactory<E> {
    public abstract String getSelectSQL();
    public List<E> findAll(Class<E> clazz) {
       // Use getSelectSQL();
    }
}

public class AlbumFactory extends BaseFactory<Album> {
    public String getSelectSQL() { return "select * from albums....."; }
}

但是,没有任何状态的物体并不是一种很好的气味。

于 2008-09-30T08:04:09.783 回答
0

如果是传一个Class给findAll,为什么不能传一个class给ModelBase中的getSelectSQL呢?

于 2008-09-30T04:48:08.460 回答
0

asterite:你的意思是 getSelectSQL 只存在于 ModelBase 中,它使用传入的类来制作表名或类似的东西?我不能这样做,因为一些模型有非常不同的选择结构,所以我不能使用通用的“select * from”+ classToTableName();。任何从模型中获取有关其选择构造的信息的尝试都会遇到与原始问题相同的问题——您需要模型的实例或一些花哨的反射。

Gizmo:我肯定会看看注释。虽然我不禁想知道在没有反思之前人们对这些问题做了什么?

于 2008-09-30T05:26:12.877 回答
0

您可以将 SQL 方法作为实例方法放在单独的类中。
然后将模型对象传递给这个新类的构造函数,并调用它的方法来获取 SQL。

于 2008-09-30T05:45:33.673 回答
0

哇——这是我之前用更一般的术语问过的一个更好的例子——如何以一种避免重复的方式为每个实现类实现静态的属性或方法,提供静态访问而不需要实例化相关的类和感觉'对'。

简短回答(Java 或 .NET):你不能。更长的答案 - 如果您不介意使用类级别注释(反射)或实例化对象(实例方法),则可以,但两者都不是真正的“干净”。

在此处查看我之前的(相关)问题:如何处理因实现类而异的静态字段 我认为答案都非常蹩脚并且没有抓住重点。你的问题措辞更好。

于 2008-09-30T07:39:55.567 回答
-1

我同意 Gizmo:您正在查看注释或某种配置文件。我会看看 Hibernate 和其他 ORM 框架(甚至可能是 log4j 之类的库!),看看它们如何处理类级元信息的加载。

并非所有事情都可以或应该以编程方式完成,我觉得这可能是其中一种情况。

于 2008-09-30T06:13:48.440 回答