【问题标题】:Alternatives to static methods in JavaJava 中静态方法的替代方案
【发布时间】:2008-09-30 04:41:36
【问题描述】:

我正在为我正在编写的 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 类方法一样工作。如果是这样,它不会工作:(

标签: java interface static


【解决方案1】:

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

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

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

现在您有几个优势。由于您的方法不再是静态的,因此您可以使用继承和覆盖的全部功能。您可以使用构造函数进行任何初始化,包括将 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 );
    

    ...

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

    // 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 实现,或执行其他操作,而无需更改您的客户端代码。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        按照建议,您可以使用注释,也可以将静态方法移至工厂对象:

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

        但是没有任何状态的对象,气味不是很好。

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

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

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

            【讨论】:

            • 那你应该看看不支持反射的语言中的ORM,比如C++。因为据我所知,几乎所有 Java ORM(如果不是全部)在某些时候都使用反射。
            • 请将这些 cmets 设为... cmets ;) 如果仅用于回答,则回答。这样,Gizmo 和 asterite 将 编辑 他们的答案以完成它们,或者将留下更多的 cmets 继续由您的问题发起的讨论
            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

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

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

              在此处查看我之前的(相关)问题:How to handle static fields that vary by implementing class 我认为答案都很蹩脚,没有抓住重点。你的问题措辞更好。

              【讨论】:

                【解决方案9】:

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

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2023-03-13
                  • 2018-09-22
                  • 2011-02-12
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多