[Résolu] Généricité et héritage

Généricité et héritage [Résolu] - Java - Programmation

Marsh Posté le 30-10-2009 à 09:44:42    

Bonjour toutes, tous,
 
je suis confronté à un problème assez sioux que je vous expose ci dessous.
Considérons d'abord la classe abstraite suivante :

Code :
  1. public abstract class Lattice<E extends Pattern> {
  2. public Lattice () {}
  3. public abstract E meet(E pattern1, E pattern2);
  4. public abstract E join(E pattern1, E pattern2);
  5. public abstract boolean isSubsuming(E pattern1, E pattern2);
  6. public abstract Vector<E> getElements();
  7. public abstract void addElement(E pattern);
  8. }


 
et la suivante :

Code :
  1. public abstract class Pattern implements Cloneable
  2. {
  3. public Pattern() {}
  4. public abstract Pattern clone();
  5. }


 
J'utilise ces deux classes de cette manière :  

Code :
  1. public class IntervalLattice extends Lattice<IntervalPattern> {
  2. Vector<IntervalPattern> elements = new Vector<IntervalPattern> ();
  3. //  ici  je donne une implémentation pour chaque méthode abstraite héritée.
  4. public boolean isSubsuming(IntervalPattern pattern1,IntervalPattern pattern2) 
  5. //....
  6. }


 
et  

Code :
  1. public class IntervalPattern extends Pattern
  2. {
  3. private double inf;
  4. private double sup;
  5. //  ici  je donne un constructeur par défaut, et un autre pour remplir les champs
  6.         // puis quelques méthodes de travail pas importantes pour notre problème
  7. }


 
 
Le problème avec ça est que je dois souvent faire des cast comme

Code :
  1. Lattice<IntervalPattern> l = ...
  2. IntervalLattice il = (IntervalLattice) l;


 
et ça c'est très mal ... pas propre, très mal réutilisable, et finalement pas générique du tout.
en fait, je cherche un moyen de n'avoir qu'une seul entité pour Lattice<E> et "ELattice".
 
J'espère avoir énoncé le problème clairement,
n'hésitez à dire sinon,
un grand merci d'avance,
cordialement,


Message édité par Krukov le 02-11-2009 à 14:42:29
Reply

Marsh Posté le 30-10-2009 à 09:44:42   

Reply

Marsh Posté le 30-10-2009 à 10:06:14    

C'est pas moche, c'est un upper cast. Tous les Lattice ne sont pas des IntervalLattice, il faut donc t'attendre à ce que la conversion échoue.

Reply

Marsh Posté le 30-10-2009 à 10:14:20    

Salut Taz, et merci.
oui, je suis bien d'accord avec toi,
mais là, ce n'est pas n'importe quel lattice,
c'est un Lattice<IntervalPattern>.
Du coup, j'ai mal modélisé, car on a de la redondance et des cast,
mais je ne trouve pas moyen de l'éviter...

 

edit : je ne dis pas que les cast sont pas bien, je dis juste que j'aimerai m'en passer


Message édité par Krukov le 30-10-2009 à 10:15:19
Reply

Marsh Posté le 30-10-2009 à 14:49:10    

Si dans ton code tu n'as qu'une seule variante de Lattice<IntervalPattern> qui est IntervalLattice, alors tu peux déclarer la variable l comme étant de type IntervalLattice, tu ne devras pas faire de cast!
 
Sinon comme dit Taz c'est que tu as plusieurs sortes de Lattice<IntervalPattern> avec lesquels tu travailles, dont l'une est IntervalLattice et là tu dois effectivement faire un cast si tu les mets toutes dans le même sac. Sinon tu peux toujours les stocker dans des collections différentes et utiliser des variables du type correspondant.
 
D'autre part si aucune classe qui implémente l'interface Lattice<E extends Pattern> (oui tu l'as déclarée comme classe abstraite mais tu devrais en faire une interface) ne déclare de méthode publique additionnelle ou qu'elle en déclare mais que tu ne les utilise pas dans la plupart des opérations, alors tu peux travailler directement dans ton code avec l'interface, c'est à dire avec des variables de type Lattice<IntervalPattern> et tu n'as aucun besoin de faire un cast sauf quand tu dois accéder à ces méthodes qui ne sont pas dans l'interface.
 
A part cela, c'est fou le nombre de personnes qui continuent à utiliser Vector.


Message édité par cbeyls le 30-10-2009 à 14:52:17
Reply

Marsh Posté le 30-10-2009 à 16:42:41    

Hello, et merci,
voici un exemple qui exhibe mieux mon problème

Code :
  1. public class Lattice<E extends Pattern> extends Vector<E> {
  2. private static final long serialVersionUID = 1L;
  3. public E meet (E p, E q)
  4. {
  5.  return (E) p.methode(q);  // c'est ici que je suis obligé de faire un cast, ce que je cherche à éviter
  6. }
  7. public static void main (String[] args){
  8.  Lattice<Interval> l  = new Lattice<Interval>();
  9.  l.add(new Interval());
  10. }
  11. }
 

J'ai donc loupé quelque chose dans la généricité, et je ne trouve pas quoi...
Merci !

 

ps : un jour, j'avais fait un test débile avec Vector et ArrayList, remplir 1000000 objets et les supprimer, et dans mes souvenir le Vector était bien plus rapide. Tu suggères quoi ? (même si c'est pas l'objet du post ;))


Message édité par Krukov le 30-10-2009 à 16:44:30
Reply

Marsh Posté le 30-10-2009 à 22:11:27    

Si la méthode renvoie un objet de type "E extends Pattern" alors pas besoin de cast, non?
 
Je n'ai pas une vue d'ensemble du projet alors difficile de t'aider plus.
 
On utilise normalement ArrayList à la place de Vector car il n'est pas synchronisé donc théoriquement plus rapide.

Reply

Marsh Posté le 30-10-2009 à 22:50:27    

Bon, après relecture je pense que tu fais trop compliqué et tu n'as pas besoin de types génériques partout. Tu devrais faire ceci:
 
Transformer Pattern en une simple interface:
 

Code :
  1. public interface Pattern extends Cloneable {
  2.   Pattern methode(Pattern q);
  3.   // Ajouter les signatures de méthodes additionnelles ici
  4. }


 
Interval implémente cette interface:
 

Code :
  1. public class Interval implements Pattern {
  2.   // Implémentation des méthodes de Pattern
  3. }


 
Pour la classe Lattice, ne pas utiliser des éléments de type "E extends Pattern" mais juste "Pattern" et travailler avec cette interface:
 

Code :
  1. public class Lattice extends ArrayList<Pattern> {
  2. private static final long serialVersionUID = 1L;
  3.  
  4. public Pattern meet (Pattern p, Pattern q) {
  5.   return p.methode(q);  // pas besoin de cast ici
  6. }
  7.  
  8. public static void main (String[] args) {
  9.   Lattice l = new Lattice();
  10.   l.add(new Interval());
  11. }
  12. }

Reply

Marsh Posté le 01-11-2009 à 21:13:59    

Merci pour ta réponse,
ok pour Pattern en interface, et pour les ArrayList.
Par contre, suivant le type de Pattern, je peux avoir le constructeur de la classe Lattice différent et l'implémentation de la méthode meet différente.
 
C'est pour ça que j'avais plus haut
 

Code :
  1. public class IntervalLattice extends Lattice<IntervalPattern>


 
c'est finalement ce que tu demandais dans ton premier message, que je n'avais pas compris à ce moment là.
 
Du coup, tu as bien raison, je fais trop compliqué et je mélange deux choses : héritage et généricité (d'où le titre du post)
et il est clair que ces deux là ne font pas bon ménage ensemble !
 
Pourquoi ce mélange ? Car je voulais forcer qu'on ne puisse mettre qu'un seul type de Pattern dans un Lattice, mais
que suivant le type de Pattern, la classe conteneur Lattice soit munie de constructeurs différents et possiblement de redéfinition et/ou d'ajout de méthodes.
 
Du coup, je suis assez déçu, ce que je veux faire n'est pas possible je pense (je me trompe peut être...)
 
Aussi, je vais revenir à du classique héritage, et quelques exceptions pour gérer le fait qu'on ne peut mettre qu'un type de Pattern  
dans un lattice (en mettant un paramètre Class<?> patternClass dans le constructeur de Lattice par exemple ?) Finalement,  
j'aurai deux hiérarchies parallèles : celle des Lattice, et celles des Pattern correspondants. Je ne sais pas trop encore ... J'y refléchis,
et merci en tout cas pour la discussion (qui n'est peut être pas close, je suis toujours ouvert à la critique).
 
 
ps : j'ai re-tester le tri de 10000000 Random Integer dans une ArrayList et dans un Vector ....
 10 sec pour vector et 8.5 sec pour une arraylist, sur un 15 ène d'essais, je passe au ArrayList ;)
 

Reply

Marsh Posté le 01-11-2009 à 23:57:59    

OK, j'ai compris ce que tu essayes de faire: tu veux qu'une implémentation spécifique de Lattice utilise une implémentation spécifique de Pattern, tout en utilisant des interfaces communes.
C'est possible à réaliser mais c'est assez compliqué à écrire!
 
En fait tu dois paramétriser les interfaces Lattice ET Pattern pour ne pas avoir à faire de cast. Accroche-toi:
 
L'interface Pattern paramétrisée:
 

Code :
  1. public interface Pattern<E extends Pattern<E>> extends Cloneable {
  2.     E methode(E q);
  3. }


 
La classe IntervalPattern implémente cette interface:
 

Code :
  1. public class IntervalPattern implements Pattern<IntervalPattern> {
  2.     @Override
  3.     public IntervalPattern methode(IntervalPattern q) {
  4.         return null;
  5.     }
  6. }


 
Ensuite l'interface Lattice qui est paramétrisée pour utiliser des objets qui implémentent Pattern:
 

Code :
  1. public interface Lattice<E extends Pattern<E>> {
  2.     public E meet(E pattern1, E pattern2);
  3.     public E join(E pattern1, E pattern2);
  4.     public boolean isSubsuming(E pattern1, E pattern2);
  5.     public List<E> getElements();
  6.     public void addElement(E pattern);
  7. }


 
Et enfin la classe IntervalLattice qui est une implémentation de Lattice acceptant uniquement des objets de type IntervalPattern:
 

Code :
  1. public class IntervalLattice implements Lattice<IntervalPattern> {
  2.  
  3.     // ...
  4.     
  5.     @Override
  6.     public IntervalPattern meet(IntervalPattern pattern1,
  7.             IntervalPattern pattern2) {
  8.         return pattern1.methode(pattern2);    // Pas de cast ici!
  9.     }
  10.  
  11. }


 
Et voilà, pas de cast et tout se tient. J'espère ne pas t'avoir donné mal de tête et te souhaite un bon travail.

Reply

Marsh Posté le 02-11-2009 à 14:41:38    

J'ai écrit et tester ce que tu viens de me donner, et je dois dire que ...
ça marche très bien, et c'est même logique. Il est évident pour moi maintenant qu'il faille passer
par de l'interface. Par contre, j'avais tester des choses du genre Pattern<E extends Pattern<E>>
mais j'avais abandonné car je veux toujours un code strict zéro warning sans faire de @suppress
Là je n'ai pas le choix, et ça me va très bien comme ça. A problème sioux, solution sioux ;)
 
Par contre, j'ai un petit soucis : j'ai besoin de faire des choses du genre :  

Code :
  1. List<Lattice> lattices = new ArrayList<IntervalLattice>();
  2. lattices.add(new IntervalLattice());


et là, boom :  

Citation :

Type mismatch: cannot convert from ArrayList<IntervalLattice> to List<Lattice>


 
Par contre, si je fais  

Code :
  1. List<Lattice> lattices = new ArrayList<Lattice>();
  2.       lattices.add(new IntervalLattice());


 
alors ca marche très bien.
 
Forcément, j'ai le même problème avec les Pattern.
 

Code :
  1. Interval c = new Interval(6);
  2. List<Pattern> A = new ArrayList<Pattern>(); // même probleme je n'ai pas le droit de mettre  new ArrayList<Interval>();
  3. A.add(a);


 
Ce n'est pas bien méchant, et je pense laisser dans l'état. Je suis juste maniaque des warning,
mais si tu vois une solution élégante, ca m'intéresse d'en discuter.  
 
En tous les cas, je marque le post comme [résolu] et te remercie vivement pour ton attention
et le temps que tu as passé sur mon problème,
Au plaisir de se lire à nouveau
 
 

Reply

Marsh Posté le 02-11-2009 à 14:41:38   

Reply

Marsh Posté le 02-11-2009 à 19:06:26    

Il n'y a pas de warning avec la solution que je t'ai proposée je pense.
 
Effectivement tu dois faire comme tu l'as écrit: instancier des
 

Code :
  1. List<Lattice> lattices = new ArrayList<Lattice>();


 
si tu veux créer des une liste d'objets Lattice, pouvant contenir des IntervalLattice ou d'autres objets qui implémentent Lattice.
 
Par contre si tu veux créer une liste contenant uniquement des objets IntervalLattice, tu écris:
 

Code :
  1. List<IntervalLattice> lattices = new ArrayList<IntervalLattice>();


 
Donc, toujours spécifier le même type générique des deux côtés et il n'y a aucun problème.


Message édité par cbeyls le 03-11-2009 à 02:40:54
Reply

Marsh Posté le 03-11-2009 à 02:28:44    

Encore un petit détail: tu peux éventuellement utiliser une classe abstraite au lieu d'une interface si tu veux fournir des implémentations communes à toutes les classes qui l'"implémentent". Evidemment si la classe abstraite n'a aucune méthode implémentée, autant la transformer en interface d'où ma suggestion. Tu peux aussi combiner les 2: faire une interface puis une classe abstraite qui implémente partiellement l'interface et qui va être étendue par d'autres classes.
 
D'un point de vue conceptuel, une interface est une vue purement "publique" et "externe" sur un objet, et une classe abstraite est plutôt vue comme une aide à l'implémentation, permettant d'éviter la répétition de code commun. La différence majeure entre les deux est qu'en java, une classe peut implémenter plusieurs interfaces mais ne peut étendre qu'une seule classe (abstraite ou non).


Message édité par cbeyls le 03-11-2009 à 02:31:10
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed