Les sources de la solution sont disponibles [ ici ].

Les commentaires :


Sur les rencontres

 La principale modification pour introduire le polymorphisme et réécrire le code des rencontres est de créer une méthode Humain rencontre(Humain h) dans la classe Humain et la redéfinir Homme et Femme. Cela permet d'écrire h1.rencontre(h2) sans avoir besoin de tester le type d'instance de h1 et h2, puisque la méthode manipulant 2 types Humain existe dans Humain. Le compilateur est donc content.

Le seul inconvénient est que dans Humain, cette méthode est quasi vide et lors de l'exécution, elle ne sera jamais appelée puisque la JVM va déterminer le type d'instance de h1 et de là, appeler soit la méthode rencontre() de Homme, soit celle de Femme.

Bien entendu, il reste quand même à tester dans ces 2 méthodes si l'humain passé en paramètre est un homme ou une femme, afin de savoir si la rencontre peut être fertile ou non.


 Sur la structure générale du moteur de jeu

Grâce aux changements mentionnés ci-dessus, il est possible de simplifie la boucle qui fait les rencontres :

  • Premièrement, on peut "sortir" le code qui sélectionne 2 humains pour le mettre dans une méthode, cf. setCouple() de la solution. Cette méthode met à jour un tableau de 2 Humain nommé couple.
  • Deuxièmement, on appelle directement la méthode rencontre() en utilisant les 2 objets stockés dans le tableau couple.

 

Le deuxième changement concerne la fin du tour de jeu où l'on appelle une nouvelle méthode qui permet de trier la population.


 Sur le tri de la population

Si l'on écrit seulement 2 méthodes compareTo(), une dans Humain, l'autre dans Homme, selon l'énoncé, le tri n'est effectivement pas correct. Le problème est tout simplement que selon l'ordre des hommes et femmes dans la liste, la méthode de tri va produire des résultats non voulus, mais logiques.

Par exemple, supposons que la liste contienne un homme, puis 1 femme, et de nouveau un homme, tous du même âge. On note ces humains h1, f2, h3. De plus, on suppose que le premier homme a un salaire plus élevé que le second. Enfin, si on suppose que la méthode de tri fait les opération suivantes :

  • comparaison de h1 et f2 => appel de la méthode compareTo() de Homme. f2 étant une femme, la méthode renvoie 0, donc on laisse h1 et f2 dans le même ordre.
  • comparaison de f2 et h3 => appel de la méthode compareTo() de Humain. La méthode renvoie 0, donc on laisse f2 et h3 dans le même ordre.

Au final, le 2ème homme n'a pas bougé, alors qu'il devrait se trouver avant le premier homme.

 

Pour tenter de régler ce problème, il suffirait de placer, pour un même âge, les femmes avant les hommes (ou l'inverse). Cela revient à une solution simple dans compareTo() de Homme : si l'objet courant a le même âge que le paramètre h ET si h est une femme, compareTo() renvoie 1. Malheureusement, on constate que cela ne règle pas le problème précédent. On obtient parfois une alternant homme/femme pour un même âge. Qui plus est, il se produit parfois une erreur du type : "Comparison method violates its general contract."

 

Cette erreur vient du fait que la méthode de tri doit faire des manipulations sur l'ordre des humains qui ne sont pas cohérentes. Par exemple, un changement d'ordre dans un sens provoque un appel à compareTo() qui va faire le changement dans le sens inverse, qui lui-même provoque l'appel à compareTo() qui refait le changement de sens, etc. La méthode de tri contient des gardes contre ce genre de situation pour éviter de boucler à l'infini.

En fait, on ne sait pas vraiment comment le tri s'opère et dans quel ordre il va prendre les éléments de la collection. Ce dont il faut être sur, c'est que le résultat de l'appel des différentes méthode compareTo() doit être strictement le même pour 2 éléments successifs.Dans le cas présent, supposons un homme h1 suivi d'une femme f2 :

  • Si la méthode de tri teste h1 vs. f2, on appelle compareTo() de Homme, qui renvoie 1 afin que la femme soit mise avant l'homme.
  • En revanche, si on teste f2 vs. h1, on appelle compareTo() de Humain, qui renvoie 0. Or, il faudrait que ça renvoie -1 pour que la femme soit mise avant l'homme.

On a donc une incohérence, d'où le message d'erreur et le tri qui se fait mal.

 

La seule façon d'éviter l'erreur ET de trier correctement est d'ajouter une méthode compareTo() dans Femme, qui renvoie -1 si on teste vs. un homme du même âge.


Sur les garçons/filles

Le fait de pouvoir faire des rencontres entre garçon & femme, ou bien fille & homme conduit quasi automatiquement au choix de faire hériter Garcon d'Homme et Fille de Femme. Sinon, on serait obligé de redéfinir rencontre() dans Garçon et Fille avec le même code que dans Homme et Femme, donc bof !

Pour éviter qu'un Garcon ait un salaire, il suffit que cet attribut soit privé dans Homme.

Pour déterminer si une instance est un Garcon/Fille, il existe principalement 3 solutions "simples" :

  • utiliser isHomme() et isFemme() et juste après tester l'âge. Ce n'est pas très "pratique".
  • créer une méthode isChild() dans Humain qui teste l'âge et renvoie true s'il est inférieur à 18. C'est pratique d'un point de vue implémentation et mais pas très POO au niveau modélisation.
  • créer 2 méthodes : isGarcon() et isFille(). Cela nécessite de les créer dans Humain et les redéfinir, ainsi que isHomme() et isFemme() dans Garcon et Fille.

 

La seconde solution ne nécessite pas de changement dans les méthodes rencontre() de Homme et Femme. Par exemple, si une femme rencontre une fille, le test if (h.isFemme()) au début de rencontre va renvoyer true, ce qui permet d'arrêter la méthode. En revanche, la 3ème solution nécessite de faire un test du type : if (h.isFemme() || h.isFille()).

 

D'un autre côté, quand il faut déterminer si 2 adolescents se rencontrent, ou quand ils ont 18 ans, les tests sont plus long à écrire qu'avec la 3ème solution. Bref, ces 2 solutions sont globalement équivalentes.

  • NB : c'est la 3ème qui est utilisée dans la correction.

 

Il existe une quatrième solution, mais elle peut prêter à confusion : dans Garcon, on redéfinit uniquement isGarcon() qui renvoie true, idem pour Fille et isFille().

Exemple :

Humain h = new Homme(...);
System.out.println(h.isHomme()); // affiche true
System.out.println(h.isGarcon()); // affiche false
Homme g = new Garcon(...);
System.out.println(g.isHomme()); // affiche true
System.out.println(g.isGarcon()); // affiche true

Cela permet de combiner les avantages des 2 solutions simples. En revanche, il faut être sur d'utiliser correctement isHomme() car on peut maintenant obtenir true malgré le fait que l'instance testé N'EST PAS un Homme.

 

Pour éviter les rencontres garçon/fille, il suffit de redéfinir la méthode rencontre() dans Garcon et Fille. Si la combinaison est garçon+fille, on arrête la méthode, sinon on utilise la super méthode. Comme indiqué dans les commentaires, la méthode redéfinie est exactement la même dans Garcon et Fille. Malheureusement, il est impossible de déplacer ce code dans Humain. On est donc obligé pour une fois d'avoir un copier/coller de code.