Alternatives to static methods in Java

2022-09-04 22:49:46

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

ModelBase is abstract & provides a bunch of static methods for finding & binding objects from the db, for example:

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

So you can do things like to get a list of all persisted albums. My problem is that in this static context, I need to get the appropriate sql string from the concrete class Album. I can't have a static method like ModelBase.findAll(Albums.class)

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

because there is no polymorphism for static methods in Java. But I don't want to make an instance method in 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, uses reflection to get the appropriate sql for the class in question:findAll()

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!)?


答案 1

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


答案 2

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. 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.

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. 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.


推荐