Java中静态方法的替代方案

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 实现类方法(或者失败的话,类常量)?


我有点困惑,为什么在有像iBatis和Hibernate这样优秀的选择时,你要编写自己的ORM。顺便说一句,我觉得你正在尝试让Java的静态方法像Ruby的类方法一样工作。如果是这样,它不会起作用:( - Alan
9个回答

4

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

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

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

现在你有了一些优势。由于你的方法不再是静态的,所以你可以使用继承和覆盖的全部功能。你可以使用构造函数进行任何初始化,包括将SQL与表相关联(稍后可以使用这些方法的SQL)。这应该解决所有以上问题,或者至少使问题变得简单得多。

似乎需要创建对象并且需要额外的内存,但与优势相比,这些都是微不足道的。几个字节的内存对于对象不会被注意到,一些构造函数调用可能需要十分钟左右。相对于优点,如果未使用表,则不需要运行初始化任何表的代码(不应调用构造函数)。你会发现它大大简化了事情。


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 );

另一种方法是从后面创建实例,并在使用实例方法时保持客户端接口不变,这些方法被标记为“protected”以避免外部调用。与前一个示例类似,您也可以这样做。
// 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实现,而无需修改客户端代码。


1

为什么不使用注释?它们非常适合您正在进行的操作:向类添加元信息(这里是SQL查询)。


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....."; }
}

但是没有任何状态的对象并不是一个很好的实践。


0
如果您正在将一个类传递给findAll,为什么不能在ModelBase中将一个类传递给getSelectSQL呢?

0

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

gizmo: 我一定会研究注释。虽然我不禁想知道在反射出现之前人们是如何处理这些问题的?


那么你应该看一下在不支持反射的语言中的ORM,比如C++。因为据我所知,几乎所有的Java ORM(如果不是全部)都在某个时候使用反射。 - gizmo
请将这些评论作为注释。答案只用于回答问题。这样,gizmo和asterite将会编辑他们的答案以补充完整,或者留下进一步的评论来继续讨论您的问题所引发的讨论。 - VonC

0
你可以将SQL方法作为单独类中的实例方法。
然后将模型对象传递到这个新类的构造函数中,并调用其方法以获取SQL。

0

哇 - 这是我之前以更一般的方式提出的问题的一个更好的例子 - 如何实现每个实现类都是静态的属性或方法,避免重复,提供静态访问而不需要实例化相关类,并且感觉“正确”。

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

请参阅我先前(相关的)问题:如何处理因实现类而异的静态字段 我认为所有答案都非常无聊,而且错过了重点。您的问题措辞得更好。


-1
我同意Gizmo的看法:你可能是在查看注释或某种配置文件。我建议你可以研究一下Hibernate和其他ORM框架(甚至可能包括像log4j这样的库),以了解它们如何处理类级元信息的加载。
并非所有东西都可以或应该通过编程来完成,我认为这可能是其中之一。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接