J'ai démarré hier puis trouvé un peu de temps pour avoir un truc à peu près présentable ce midi.
https://github.com/aesteve/groovyquarium
Les trucs à noter :
Traits
Le projet utilise les traits pour tout ce qui est "comportement" (méthode de reproduction, régime alimentaire).
Ni plus ni moins qu'un design pattern strategy, simplement un trait peut hériter d'un autre trait. Ce qui permet de décrire des comportement (comme dans cet exemple) de façon assez simple, puis de composer une classe à partir de plusieurs traits.
Du coup, contrairement à pas mal d'exemple en Java où on va stocker le type de poisson en tant qu'énumération par exemple, on peut directement écrire ça.
On aurait aimé avoir l'union de types comme ci-dessus (ou en Ceylon) ça serait encore plus élégant, mais c'est déjà un peu mieux que la PoissonFactory
qui contient les règles de "si c'est une carpe alors son régime alimentaire c'est végétérien"
Des trucs bien pratiques
Méta-programmation
Un truc qu'on écrit plusieurs fois dans cet exercice : list.get(rand.nextInt(list.size()))
. ce qui est pas très joli à se trimbaler dans le code, d'où cette idée.
On ajoute une méthode getRandomMember
sur toutes les listes, histoire de pas avoir à le réécrire à chaque fois.
Surcharge d'opérateurs
void add(Plant p) { this.plants.add(p) }
. Finalement c'est quoi ? Un ajout ? Bon bah on n'a qu'à écrire aquarium + p
ou aquarium += p
si ça vous plaît mieux. Pour cela, il suffire de redéfinir l'opérateur plus
Même chose pour ++
(méthode next
)
Des conventions utiles
A chaque fois qu'on tire au sort, on va éviter de tirer un poisson mort, au cas où le ménage ait mal été fait dans notre aquarium (tant qu'à faire on fera le ménage à la fin du tour d'ailleurs, même si c'est pas glop de laisser des cadavres au fond du bassin, c'est plus pratique que d'aller chercher son épuisette à chaque fois).
Du coup, on va demander la liste des poissons/plantes vivants. Invoquer une méthode à chaque fois, c'est un peu lourd à écrire, alors qu'au final, ça ressemble à un attribut de l'aquarium (on pourrait d'ailleurs mettre cette liste en cache par exemple).
Du coup, il suffit d'écrire ça pour que, par convention du langage, aquarium.livingPlants
fasse appel à cette méthode (si il ne trouve pas la propriété, il appelle ce getter
).
Comme tous les getter instrumentés, c'est à utiliser avec prudence quand même… Ici c'était essentiellement pour présenter cette convention.
EDIT : au départ j'avais profité d'avoir ce seul point d'accès au tirage aléatoire d'un poisson pour rajouter :
| Fish randomFish(Fish self) {
def list = livingFishes.findAll { !it.is(self) } as List
list.randomMember
}
|
Parce que ça me semblait logique mais ça ne respectait pas l'énoncé.
Le "tour" d'aquarium est écrit en quasiment une ligne
https://github.com/aesteve/groovyquarium/blob/master/src/main/groovy/com/github/aesteve/groovyquarium/Aquarium.groovy#L65
L'opérateur plus des listes nous permet de passer outre la fameuse ConcurrentModificationException, puisqu'il renvoie une nouvelle liste. Et on fait un "next" sur chacun des être vivants de l'aquarium. Easy.
Des tests unitaires
Non pas qu'il y en ait vraiment besoin dans ce projet, mais ça m'a permis de vérifier point par point que je respectais l'énoncé (même si j'ai du zapper quelques trucs).
Le petite démo est elle-même un (faux) test unitaire. Plus rapide qu'écrire un main
et tout le tralala.
Voilà, c'est à peu près tout. Il manque sans doute plein de trucs (notamment les I/O mais c'est pas franchement ce qui fait le charme de l'exercice) et y'a sans doute moyen de modéliser ça encore mieux, j'ai pas réfléchi des masses.