13 votes

Alternatives aux méthodes statiques en Java

Je suis en train de créer un mini ORM pour un programme Java que j'écris... il y a une classe pour chaque table de ma base de données, toutes héritant de ModelBase .

ModelBase est abstrait et fournit un ensemble de méthodes statiques pour trouver et lier des objets de la base de données, par exemple :

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

Vous pouvez donc faire des choses comme ModelBase.findAll(Albums.class) pour obtenir une liste de tous les albums persistants. Mon problème est que dans ce contexte statique, je dois obtenir la chaîne sql appropriée de la classe concrète Album. Je ne peux pas avoir une méthode statique comme

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

car il n'y a pas de polymorphisme pour les méthodes statiques en Java. Mais je ne veux pas faire getSelectSQL() une méthode d'instance dans Album parce que je dois alors créer une instance de celui-ci pour obtenir une chaîne dont le comportement est vraiment statique.

En ce moment, findAll() utilise la réflexion pour obtenir le sql approprié pour la classe en question :

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

Mais c'est plutôt dégoûtant.

Alors, des idées ? C'est un problème général que je rencontre sans cesse : l'impossibilité de spécifier des méthodes statiques abstraites dans les classes ou les interfaces. Je sais que pourquoi Le polymorphisme des méthodes statiques ne fonctionne pas et ne peut pas fonctionner, mais cela ne m'empêche pas de vouloir l'utiliser à nouveau !

Existe-t-il un modèle/construction qui me permette de m'assurer que les sous-classes concrètes X et Y implémentent une méthode de classe (ou à défaut, une constante de classe !) ?

4voto

DJClayworth Points 11288

La statique est la mauvaise chose à utiliser ici.

La statique conceptuelle est erronée car elle ne concerne que les services qui ne correspondent pas à un objet réel, physique ou conceptuel. Vous avez un certain nombre de tables, et chacune d'entre elles devrait être représentée par un objet réel dans le système, et pas seulement par une classe. Cela semble un peu théorique, mais cela a des conséquences réelles, comme nous allons le voir.

Chaque table est d'une classe différente, et c'est bien ainsi. Puisque vous ne pouvez avoir qu'une seule instance de chaque table, limitez le nombre d'instances de chaque classe à une seule (utilisez un drapeau - n'en faites pas un Singleton). Faites en sorte que le programme crée une instance de la classe avant d'accéder à la table.

Maintenant, vous avez quelques avantages. Vous pouvez utiliser toute la puissance de l'héritage et de l'overriding puisque vos méthodes ne sont plus statiques. Vous pouvez utiliser le constructeur pour effectuer toute initialisation, y compris l'association de SQL avec la table (SQL que vos méthodes pourront utiliser plus tard). Tous les problèmes décrits ci-dessus devraient disparaître, ou du moins devenir beaucoup plus simples.

Il semble qu'il y ait un travail supplémentaire pour créer l'objet, ainsi qu'une mémoire supplémentaire, mais c'est vraiment insignifiant par rapport aux avantages. Quelques octets de mémoire pour l'objet ne seront pas remarqués, et une poignée d'appels de constructeurs prendra peut-être dix minutes à ajouter. En contrepartie, le code d'initialisation des tables n'a pas besoin d'être exécuté si la table n'est pas utilisée (le constructeur ne doit pas être appelé). Vous trouverez que cela simplifie beaucoup les choses.

2voto

OscarRyz Points 82553

Bien que je sois totalement d'accord sur le point "Static n'est pas la bonne chose à utiliser ici", je comprends un peu ce que vous essayez d'aborder ici. Le comportement d'instance devrait être le meilleur moyen de travailler, mais si vous insistez, c'est ce que je ferais :

A partir de votre commentaire "J'ai besoin de créer une instance de celui-ci juste pour obtenir une chaîne qui est vraiment statique dans son comportement".

Ce n'est pas tout à fait correct. Si vous regardez bien, vous ne changez pas le comportement de votre classe de base, vous changez juste le paramètre d'une méthode. En d'autres termes, vous changez les données, pas l'algorithme.

L'héritage est plus utile lorsqu'une nouvelle sous-classe veut changer la façon dont une méthode fonctionne, si vous avez juste besoin de changer les "données" que la classe utilise pour fonctionner, une approche comme celle-ci ferait probablement l'affaire.

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;

    }
}

En d'autres termes, faites correspondre toutes les déclarations avec un Map. La prochaine étape "évidente" est de charger la carte à partir d'une ressource externe, comme un fichier de propriétés, un fichier xml ou même (pourquoi pas) une table de base de données, pour plus de flexibilité.

De cette façon, les clients de votre classe (et vous-même) sont satisfaits, car vous n'avez pas besoin de "créer une instance" pour faire le travail.

// Client usage:

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

...

Une autre approche consiste à créer les instances à partir de l'arrière-plan, et à conserver l'interface client intacte tout en utilisant les méthodes d'instance, les méthodes étant marquées comme "protégées" pour éviter toute invocation externe. De manière similaire à l'exemple précédent, vous pouvez également faire ceci

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

Et vous n'avez pas besoin de modifier le code client, tout en bénéficiant de la puissance du polymorphisme.

// Client usage:

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

...

J'espère que cela vous aidera.

Une dernière remarque sur l'utilisation de List vs. ArrayList. Il est toujours préférable de programmer en fonction de l'interface plutôt que de l'implémentation, ce qui rend votre code plus flexible. Vous pouvez utiliser une autre implémentation de List qui est plus rapide, ou qui fait autre chose, sans changer votre code client.

1voto

gizmo Points 8528

Pourquoi ne pas utiliser les annotations ? Elles conviennent très bien à ce que vous faites : ajouter des méta-informations (ici une requête SQL) à une classe.

1voto

Bruno De Fraine Points 11478

Comme suggéré, vous pouvez utiliser des annotations ou déplacer les méthodes statiques vers des objets de type "factory" :

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

Mais ce n'est pas une très bonne odeur que d'avoir des objets sans aucun état.

0voto

asterite Points 3218

Si vous passez une classe à findAll, pourquoi ne pouvez-vous pas passer une classe à getSelectSQL dans ModelBase ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X