Java中静态方法的替代方法
我正在为正在编写的Java程序制作一个微型ORM ... db中的每个表都有一个类,所有表都继承自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()"成为"相册"中的实例方法,因为那样我就需要为其创建一个实例,以获取行为上真正静态的字符串。
目前,findAll()
使用反射来为所讨论的类获取适当的sql:
select_sql = (String)cast_to_class.getDeclaredMethod("getSelectSql", new Class[]{} ).invoke(null, null);
但这真是太过分了。
有什么想法吗?这是一个普遍的问题,我一次又一次地无法在类或者接口中指定抽象的静态方法。我知道为什么静态方法多态性不能且不能工作,但这并不能阻止我再次使用它!
是否有任何模式/构造可以让我确保具体的子类X和Y实现类方法(或者失败,类常量!)?
解决方案
如果要传递一个类给findAll,为什么不能在ModelBase中将一个类传递给getSelectSQL?
为什么不使用注释?它们非常适合我们正在执行的操作:向类添加元信息(此处为SQL查询)。
asterite:我们是说getSelectSQL仅存在于ModelBase中,并且它使用传入的类来创建表名或者类似的东西吗?
我不能这样做,因为某些模型具有与众不同的选择构造,所以我不能使用通用的" select * from" + classToTableName();。从模型获取有关其选择构造的信息的任何尝试都会遇到与原始问题相同的问题,我们需要模型的实例或者一些奇特的反映。
gizmo:我一定会看看注释。尽管我忍不住想知道人们在进行反思之前是如何处理这些问题的?
我们可以将SQL方法作为实例方法放在单独的类中。
然后将模型对象传递到这个新类的构造函数中,并调用其方法以获取SQL。
我同意Gizmo:我们正在查看批注或者某种配置文件。我将研究Hibernate和其他ORM框架(甚至可能还有log4j之类的库!),以了解它们如何处理类级元信息的加载。
并不是所有的事情都可以或者应该以编程方式完成,我认为这可能是其中一种情况。
哇,这是一个更好的例子,我以前更笼统地问过如何对每个实现类实现静态的属性或者方法,从而避免重复,无需实例化相关类即可提供静态访问,并且感觉"正确" '。
简短答案(Java或者.NET):不能。
如果我们不介意使用类级别的批注(反射)或者实例化对象(实例方法),但都不是真正的"干净"方法,则可以得到更长的答案。
在这里看到我以前的(相关)问题:如何处理因实现类而异的静态字段
我以为答案都真是and脚,错过了重点。问题措辞要好得多。
如建议的那样,我们可以使用批注,也可以将静态方法移至工厂对象:
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....."; } }
但是拥有没有任何状态的物体并不是一种很好的气味。
静态是在这里使用错误的东西。
从概念上讲,静态是错误的,因为它仅适用于与实际对象(物理或者概念上)不对应的服务。我们有许多表,每个表都应由系统中的实际对象表示,而不仅仅是类。听起来似乎有点理论,但会产生实际的后果,我们将看到。
每个表都属于不同的类,没关系。由于每个表只能有一个,因此将每个类的实例数限制为一个(使用标志不要将其设为Singleton)。使程序在访问表之前创建该类的实例。
现在我们有几个优点。由于方法不再是静态的,因此可以使用继承和覆盖的全部功能。我们可以使用构造函数进行任何初始化,包括将SQL与表关联(方法以后可以使用的SQL)。这应该使我们上面的所有问题都消失了,或者至少变得简单了很多。
似乎必须创建对象和额外的内存需要做更多的工作,但是与优点相比,这确实是微不足道的。不会注意到该对象的几个字节的内存,并且少量构造函数调用可能需要十分钟才能添加。这样做的好处是,如果不使用表,则无需运行初始化任何表的代码(不应调用构造函数)。我们会发现它简化了很多事情。
尽管我完全同意"在这里使用静态数据是错误的事情",但我有点理解我们要在此处解决的问题。实例行为仍然应该是工作方式,但是如果我们坚持这是我会做的事情:
从评论开始"我需要创建它的一个实例,只是为了获得行为上真正静态的字符串"
这不是完全正确的。如果看起来不错,我们将不会更改基类的行为,而只是更改方法的参数。换句话说,我们正在更改数据,而不是算法。
当新的子类想要更改方法的工作方式时,继承会更有用,如果我们只需要更改该类用于工作的"数据",则可能会达到这种目的。
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实现,或者执行其他操作,而无需更改客户端代码。