简体   繁体   English

Java中静态方法的替代方案

[英]Alternatives to static methods in Java

I'm making a mini ORM for a Java program I'm writing... there is a class for each table in my db, all inheriting from ModelBase . 我正在为我编写的Java程序制作一个迷你ORM ...我的数据库中的每个表都有一个类,都继承自ModelBase

ModelBase is abstract & provides a bunch of static methods for finding & binding objects from the db, for example: ModelBase是抽象的,提供了一堆静态方法,用于从db中查找和绑定对象,例如:

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

So you can do things like ModelBase.findAll(Albums.class) to get a list of all persisted albums. 因此,您可以执行类似ModelBase.findAll(Albums.class)以获取所有持久专辑的列表。 My problem is that in this static context, I need to get the appropriate sql string from the concrete class Album. 我的问题是在这个静态上下文中,我需要从具体类Album中获取相应的sql字符串。 I can't have a static method like 我不能有像这样的静态方法

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

because there is no polymorphism for static methods in Java. 因为Java中没有静态方法的多态性。 But I don't want to make getSelectSQL() an instance method in Album because then I need to create an instance of it just to get a string that is really static in behavior. 但是我不想让getSelectSQL()成为Album的实例方法,因为我需要创建它的一个实例,只是为了得到一个在行为上非常静态的字符串。

At the moment, findAll() uses reflection to get the appropriate sql for the class in question: 目前, findAll()使用反射来获取相关类的相应sql:

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

But that's pretty gross. 但这非常糟糕。

So any ideas? 那么任何想法? It's a general problem I'm having time and time again - the inability to specify abstract static methods in classes or interfaces. 这是我一次又一次的一般问题 - 无法在类或接口中指定抽象静态方法。 I know why static method polymorphism doesn't and can't work, but that doesn't stop me from wanting to use it time again! 我知道为什么静态方法多态不会也无法工作,但这并不能阻止我再次使用它!

Is there any pattern/construct that allows me to ensure that concrete subclasses X and Y implement a class method(or failing that, a class constant!)? 是否有任何模式/构造允许我确保具体的子类X和Y实现一个类方法(或者失败,类常量!)?

Static is the wrong thing to be using here. 静态在这里使用是错误的。

Conceptually static is wrong because it's only for services that don't correspond to an an actual object, physical or conceptual. 概念上静态是错误的,因为它仅适用于与实际对象(物理或概念)不对应的服务。 You have a number of tables, and each should be represented by an actual object in the system, not just be a class. 您有许多表,每个表都应该由系统中的实际对象表示,而不仅仅是一个类。 That sounds like it's a bit theoretical but it has actual consequences, as we'll see. 这听起来像是理论上的,但它有实际的后果,正如我们将要看到的那样。

Each table is of a different class, and that's OK. 每个表都是不同的类,这没关系。 Since you can only ever have one of each table, limit the number of instances of each class to one (use a flag - don't make it a Singleton). 由于您只能拥有每个表中的一个,因此将每个类的实例数限制为一个(使用标志 - 不要使其成为单例)。 Make the program create an instance of the class before it accesses the table. 使程序在访问表之前创建该类的实例。

Now you have a couple of advantages. 现在你有几个优点。 You can use the full power of inheritance and overriding since your methods are no longer static. 您可以使用继承和覆盖的全部功能,因为您的方法不再是静态的。 You can use the constructor to do any initialisation, including associating SQL with the table (SQL that your methods can use later). 您可以使用构造函数进行任何初始化,包括将SQL与表关联(您的方法稍后可以使用的SQL)。 This should make all your problems above go away, or at least get much simpler. 这应该使你上面的所有问题消失,或者至少变得更简单。

It seems like there is extra work in having to create the object, and extra memory, but it's really trivial compared with the advantages. 似乎在创建对象和额外内存方面还有额外的工作,但与优点相比,它确实是微不足道的。 A few bytes of memory for the object won't be noticed, and a handful of constructor calls will take maybe ten minutes to add. 该对象的几个字节的内存将不会被注意到,并且一些构造函数调用将花费大约10分钟来添加。 Against that is the advantage that code to initialise any tables doesn't need to be run if the table isn't used (the constructor shouldn't be called). 与此相反的是,如果不使用表(不应该调用构造函数),则不需要运行初始化任何表的代码。 You will find it simplifies things a lot. 你会发现它简化了很多事情。

Albeit, I totally agree in the point of "Static is the wrong thing to be using here", I kind of understand what you're trying to address here. 尽管如此,我完全同意“静态是在这里使用的错误”,我有点理解你在这里要解决的问题。 Still instance behavior should be the way to work, but if you insist this is what I would do: 仍然实例行为应该是工作的方式,但如果你坚持这是我会做的:

Starting from your comment "I need to create an instance of it just to get a string that is really static in behaviour" 从您的评论开始“我需要创建一个实例,只是为了获得一个在行为中非常静态的字符串”

It is not completely correct. 这不完全正确。 If you look well, you are not changing the behavior of your base class, just changing the parameter for a method. 如果你看起来很好,你不会改变你的基类的行为,只是改变方法的参数。 In other words you're changing the data, not the algorithm. 换句话说,您正在更改数据,而不是算法。

Inheritance is more useful when a new subclass wants to change the way a method works, if you just need to change the "data" the class uses to work probably an approach like this would do the trick. 当一个新的子类想要改变一个方法的工作方式时,继承会更有用,如果你只需要改变该类用来工作的“数据”,那么这样的方法就可以了。

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;

    }
}

That is, map all the statements with a Map. 也就是说,使用Map映射所有语句。 The "obvious" next step to this is to load the map from an external resource, such as a properties file, or a xml or even ( why not ) a database table, for extra flexibility. “明显的”下一步是从外部资源加载地图,例如属性文件,xml或甚至(为什么不)数据库表,以获得额外的灵活性。

This way you can keep your class clients ( and your self ) happy, because you don't needed "creating an instance" to do the work. 通过这种方式,您可以让您的班级客户(和您的自己)感到高兴,因为您不需要“创建实例”来完成工作。

// Client usage:

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

... ...

Another approach is to create the instances from behind, and keep your client interface intact while using instance methods, the methods are marked as "protected" to avoid having external invocation. 另一种方法是从后面创建实例,并在使用实例方法时保持客户端接口完整,这些方法被标记为“受保护”以避免外部调用。 In a similar fashion of the previous sample you can also do this 与前一个示例类似,您也可以这样做

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

And you don't need to change the client code, and still have the power of polymorphism. 而且您不需要更改客户端代码,并且仍然具有多态性的强大功能。

// Client usage:

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

... ...

I hope this helps. 我希望这有帮助。

A final note on using List vs. ArrayList. 关于使用List vs. ArrayList的最后一点。 It is always better to program to the interface than to the implementation, this way you make your code more flexible. 编程到界面总是比实现更好,这样可以使代码更灵活。 You can use another List implementation that is faster, or does something else, without changing your client code. 您可以在不更改客户端代码的情况下使用更快或其他更新的List实现。

Why not using annotations? 为什么不使用注释? They fitpretty well what you're doing: to add meta-information (here an SQL query) to a class. 它们非常适合您正在做的事情:将元信息(此处为SQL查询)添加到类中。

As suggested, you could use annotations, or you could move the static methods to factory objects: 如建议的那样,您可以使用注释,或者可以将静态方法移动到工厂对象:

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

But it is not a very good smell to have objects without any state. 但是没有任何状态的物体不是很好的气味。

如果要将一个Class传递给findAll,为什么不能在ModelBase中将类传递给getSelectSQL?

asterite: do you mean that getSelectSQL exists only in ModelBase, and it uses the passed in class to make a tablename or something like that? asterite:你的意思是getSelectSQL只存在于ModelBase中,并且它使用传入的类来创建一个表名或类似的东西? I cant do that, because some of the Models have wildy differeing select constructs, so I cant use a universal "select * from " + classToTableName();. 我不能这样做,因为有些模型有很多不同的选择结构,所以我不能使用通用的“select * from”+ classToTableName();. And any attempt to get information from the Models about their select construct runs into the same problem from the original question - you need an instance of the Model or some fancy reflection. 任何从模型中获取有关其select结构的信息的尝试都会遇到与原始问题相同的问题 - 您需要一个模型实例或一些花哨的反射。

gizmo: I will definatly have a look into annotations. Gizmo:我肯定会看一下注释。 Although I cant help but wonder what people did with these problems before there was reflection? 虽然我无法帮助但想知道人们在反思之前对这些问题做了什么?

You could have your SQL methods as instance methods in a separate class. 您可以将SQL方法作为实例方法放在单独的类中。
Then pass the model object into the constructor of this new class and call its methods for getting SQL. 然后将模型对象传递给这个新类的构造函数,并调用其获取SQL的方法。

Wow - this is a far better example of something I asked previously in more general terms - how to implement properties or methods that are Static to each implementing class in a way that avoids duplication, provides Static access without needing to instantiate the class concerned and feels 'Right'. 哇 - 这是我之前用更一般术语提出的一个更好的例子 - 如何以避免重复的方式为每个实现类实现静态属性或方法,提供静态访问而无需实例化相关类和感觉'对'。

Short answer (Java or .NET): You can't. 简答(Java或.NET):你不能。 Longer answer - you can if you don't mind to use a Class level annotation (reflection) or instantiating an object (instance method) but neither are truly 'clean'. 更长的答案 - 如果您不介意使用类级别注释(反射)或实例化对象(实例方法),但它们都不是真正的“干净”。

See my previous (related) question here: How to handle static fields that vary by implementing class I thought the answers were all really lame and missed the point. 在这里查看我之前的(相关)问题: 如何处理因实现类而变化的静态字段我认为答案都非常蹩脚而且错过了重点。 Your question is much better worded. 你的问题措辞要好得多。

I agree with Gizmo: you're either looking at annotations or some sort of configuration file. 我同意Gizmo:你要么注意注释还是某种配置文件。 I'd take a look at Hibernate and other ORM frameworks (and maybe even libraries like log4j!) to see how they handle loading of class-level meta-information. 我将看看Hibernate和其他ORM框架(甚至可能是像log4j这样的库!)来看看它们如何处理类级元信息的加载。

Not everything can or should be done programmatically, I feel this may be one of those cases. 并非一切都可以或应该以编程方式完成,我觉得这可能是其中一种情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM