Question

Je crée un mini ORM pour un programme Java, j'écris ... il existe une classe pour chaque table dans ma base de données, toutes héritant de ModelBase .

ModelBase est abstract & amp; fournit un tas de méthodes statiques pour trouver & amp; 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 utiliser des éléments tels que ModelBase.findAll (Albums.class) pour obtenir la 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 de getSelectSQL () une méthode d'instance dans Album car il me faut alors en créer une instance uniquement pour obtenir une chaîne réellement statique. comportement.

Pour le 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 assez dégoûtant.

Alors, des idées? C'est un problème général que j'éprouve encore et encore - l'impossibilité de spécifier des méthodes statiques abstraites dans des classes ou des interfaces. Je sais pourquoi le polymorphisme de méthode statique 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 / une 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!)?

Était-ce utile?

La solution

Bien que je sois tout à fait d’accord sur le point "Statique, c’est la mauvaise chose à utiliser ici", je comprends un peu ce que vous essayez de traiter ici. Le comportement par exemple devrait être la manière de travailler, mais si vous insistez, c’est ce que je ferais:

À partir de votre commentaire, "je dois en créer une instance uniquement pour obtenir une chaîne dont le comportement est vraiment statique".

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

L'héritage est plus utile lorsqu'une nouvelle sous-classe souhaite modifier le fonctionnement d'une méthode, s'il vous suffit de modifier l'option " données " la classe utilise probablement une approche comme celle-ci pour faire 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, mappez tous les énoncés avec une carte. Le " évident " La prochaine étape consiste à charger la carte à partir d’une ressource externe, telle qu’un fichier de propriétés, un fichier XML ou même (pourquoi pas) une table de base de données, pour une flexibilité accrue.

De cette façon, vous pouvez garder vos clients de classe (et votre auto) heureux, car vous n'avez pas besoin de "créer une instance". faire le travail.

// Client usage:

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

...

Une autre approche consiste à créer les instances à partir de l'arrière et à conserver votre interface client intacte lors de l'utilisation de méthodes d'instance. Les méthodes sont marquées comme "protégées". pour éviter d'avoir une invocation externe. De manière similaire à l'exemple précédent, vous pouvez également le faire

.
// 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 changer le code client, vous avez toujours le pouvoir 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 note finale sur l’utilisation de List vs. ArrayList. Il est toujours préférable de programmer pour l'interface que pour l'implémentation, de cette façon vous rendrez votre code plus flexible. Vous pouvez utiliser une autre implémentation de List qui est plus rapide ou fait autre chose sans changer votre code client.

Autres conseils

La statique est la mauvaise chose à utiliser ici.

Conceptuellement, statique n'est pas correct, car il ne s'applique qu'aux services qui ne correspondent pas à un objet réel, physique ou conceptuel. Vous avez un certain nombre de tables et chacune d’elles devrait être représentée par un objet réel dans le système, et non pas simplement être une classe. Cela semble un peu théorique, mais les conséquences sont réelles, comme nous le verrons.

Chaque table appartient à une classe différente, et ce n'est pas grave. Etant donné que vous ne pouvez jamais avoir qu'un seul de chaque tableau, limitez le nombre d'instances de chaque classe à un (utilisez un indicateur - n'en faites pas un Singleton). Demandez au programme de créer une instance de la classe avant qu’elle n’accède à la table.

Maintenant, vous avez quelques avantages. Vous pouvez utiliser toute la puissance de l'héritage et de la substitution puisque vos méthodes ne sont plus statiques. Vous pouvez utiliser le constructeur pour effectuer n'importe quelle initialisation, y compris en associant SQL à la table (SQL que vos méthodes pourront utiliser ultérieurement). Cela devrait faire en sorte que tous vos problèmes ci-dessus disparaissent ou au moins deviennent beaucoup plus simples.

Il semble que la création de l’objet nécessite davantage de travail et plus de mémoire, mais c’est vraiment trivial par rapport aux avantages. Quelques octets de mémoire pour l'objet ne seront pas remarqués, et quelques appels de constructeur prendront peut-être dix minutes. Par contre, l'avantage est que le code permettant d'initialiser une table n'a pas besoin d'être exécuté si la table n'est pas utilisée (le constructeur ne doit pas être appelé). Vous constaterez que cela simplifie beaucoup les choses.

Pourquoi ne pas utiliser les annotations? Ils correspondent à ce que vous faites: ajouter des méta-informations (ici une requête SQL) à une classe.

Comme suggéré, vous pouvez utiliser des annotations ou déplacer les méthodes statiques vers des objets d'usine:

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 d’avoir des objets sans aucun état.

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

asterite: voulez-vous dire que getSelectSQL n'existe que dans ModelBase et qu'il utilise la classe passée en classe pour créer un nom de table ou quelque chose du genre? Je ne peux pas le faire, car certains modèles ont des constructions sauvages très différentes, de sorte que je ne peux pas utiliser un paramètre "select * from" universel. + classToTableName () ;. Et toute tentative pour obtenir des informations des modèles sur leur construction de sélection pose le même problème que la question initiale: vous avez besoin d'une instance du modèle ou d'une réflexion sophistiquée.

gizmo: Je vais certainement regarder les annotations. Bien que je ne puisse m'empêcher de me demander ce que les gens faisaient avec ces problèmes avant qu'il y ait une réflexion?

Vous pourriez avoir vos méthodes SQL comme méthodes d'instance dans une classe séparée.
Ensuite, passez l'objet de modèle dans le constructeur de cette nouvelle classe et appelez ses méthodes pour obtenir le code SQL.

Wow - c’est un exemple bien meilleur de ce que j’avais demandé précédemment en termes plus généraux - comment implémenter des propriétés ou des méthodes statiques pour chaque classe d’implémentation de manière à éviter les doublons, fournit un accès statique sans avoir à instancier la classe concerné et se sent 'droit'.

Réponse courte (Java ou .NET): Vous ne pouvez pas. Réponse plus longue: vous pouvez utiliser une annotation de niveau classe (réflexion) ou une instanciation d'un objet (méthode d'instance) sans que cela ne vous dérange pas, mais aucun d'entre eux n'est vraiment "propre".

Voir ma précédente question (liée) ici: Comment gérer les champs statiques qui varient selon la classe d'implémentation Je pensais que les réponses étaient vraiment boiteuses et j'ai manqué le point. Votre question est beaucoup mieux libellée.

Je suis d'accord avec Gizmo: vous regardez soit des annotations, soit une sorte de fichier de configuration. J’aimerais jeter un coup d’œil sur Hibernate et d’autres frameworks ORM (et peut-être même des bibliothèques comme log4j!) Pour voir comment ils gèrent le chargement des méta-informations au niveau classe.

Tout ne peut pas ou ne devrait pas être fait par programme, je pense que cela pourrait être l'un de ces cas.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top