我正在为我编写的Java程序制作一个迷你ORM ...我的数据库中的每个表都有一个类,所有这些都继承自ModelBase
。
ModelBase
是抽象的&提供了一堆静态方法来查找&来自db的绑定对象,例如:
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实现一个类方法(或者失败,类常量!)?
答案 0 :(得分:4)
在这里使用静态是错误的。
概念上静态是错误的,因为它仅适用于与实际对象(物理或概念)不对应的服务。您有许多表,每个表都应该由系统中的实际对象表示,而不仅仅是一个类。这听起来像是理论上的,但它有实际后果,正如我们所见。
每个表都是不同的类,这没关系。由于您只能拥有每个表中的一个,因此将每个类的实例数限制为一个(使用标志 - 不要使其成为单例)。使程序在访问表之前创建该类的实例。
现在你有几个优点。您可以使用继承和覆盖的全部功能,因为您的方法不再是静态的。您可以使用构造函数进行任何初始化,包括将SQL与表关联(您的方法稍后可以使用的SQL)。这应该会使你上面的所有问题消失,或者至少变得更加简单。
似乎在创建对象和额外内存方面还有额外的工作,但与优点相比,它确实是微不足道的。该对象的几个字节的内存将不会被注意到,并且一些构造函数调用将花费大约10分钟来添加。与此相反的是,如果不使用表(不应该调用构造函数),则不需要运行初始化任何表的代码。你会发现它简化了很多事情。
答案 1 :(得分: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 vs. ArrayList的最后说明。编程到界面总是比实现更好,这样可以使代码更灵活。您可以在不更改客户端代码的情况下使用速度更快或执行其他操作的其他List实现。
答案 2 :(得分:1)
为什么不使用注释?它们非常适合您正在做的事情:将元信息(此处为SQL查询)添加到类中。
答案 3 :(得分: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....."; }
}
但是没有任何状态的物体不是很好的气味。
答案 4 :(得分:0)
如果要将一个Class传递给findAll,为什么不能在ModelBase中将类传递给getSelectSQL?
答案 5 :(得分:0)
asterite:你的意思是getSelectSQL只存在于ModelBase中,它使用传入的类来创建一个表名或类似的东西? 我不能这样做,因为有些模型有很多不同的选择结构,所以我不能使用通用的“select * from”+ classToTableName();.任何从模型中获取有关其select结构的信息的尝试都会遇到原始问题中的相同问题 - 您需要一个模型实例或一些花哨的反射。
小发明:我肯定会看一下注释。虽然我无法帮助但想知道人们在反思之前对这些问题做了什么?答案 6 :(得分:0)
您可以将SQL方法作为实例方法放在单独的类中 然后将模型对象传递给这个新类的构造函数,并调用其获取SQL的方法。
答案 7 :(得分:0)
哇 - 这是我之前用更一般术语提出的一个更好的例子 - 如何以避免重复的方式为每个实现类实现静态属性或方法,提供静态访问而无需实例化类关心并感觉'对'。
简短回答(Java或.NET):你不能。 更长的答案 - 如果您不介意使用类级别注释(反射)或实例化对象(实例方法),但两者都不是真正“干净”,您可以这样做。
请在此处查看我之前的(相关)问题:How to handle static fields that vary by implementing class 我认为答案都非常蹩脚而且错过了重点。你的问题措辞要好得多。
答案 8 :(得分:-1)
我同意Gizmo:你要么注意注释还是某种配置文件。我将看看Hibernate和其他ORM框架(甚至可能是像log4j这样的库!)来看看它们如何处理类级元信息的加载。
并非一切都可以或应该以编程方式完成,我觉得这可能是其中一种情况。