Interface et Héritage en Java : Le TamagoTux

J’ai écris précédément un article sur les interfaces, avec des exemples en Java. Voici un autre « exemple », tiré d’un projet personnel, d’utilisation des interfaces, de l’heritage de classes abstraites, de types abstraits. Il s’agit en fait de la partie logique d’un petit jeu, Tamagotux, permettant d’élever votre tux, votre GNU, votre BSD daemon ou Puffy (BlowFish OpenBSD).

Dans cet exemple, l’implémentation n’est modélisé que pour Tux. Nous avons donc une interface Animals, qui fournit les méthodes public de l’animal. On créera ainsi Tux de la manière suivante :

Animals Tamagotux = new Tux() ;

L’interface propose plusieurs méthodes.

Les premières permettent de récupérée ou ajouter des Attribute() à Tux en passant en argument son nom. La méthode pour récupérer un Attribute() retourne un objet Attribute(), son interface est vue plus bas. On peut ensuite locker ou unlocker les actions réalisables sur l’animal. Ainsi, on ne pourra, par exemple, pas lancer le thread ‘Jouer’, lier a l’Attribute() Fun() pendant qu’il mange. On peut ensuite grâce aux deux méthodes [get/set]CurrentAction(), récupérer ou définir l’action courante de Tux. Le getters est plus utile que le setters, mais pour, par exemple stopper une action, il est plus simple de tout simplement détruire l’action courante en la passant à null que de envoyer un signal au thread. Enfin, Animals() expose une dernière méthode, vivre(), qui est lié à un thread, non modélisé ici, qui en fait diminue progressivement les Attribute() défini (faim, fatigue, amusement, etc) et rend l’animal « actionnable ». Une fois l’animal en vie, le boolean IsInitialized passe à true et il devient impossible de lui rajouter des Attribute(). Passons maintenant à la classe abstraite abstractAnimals(), elle a pour but d’implémenter les getters et setters qui sont les mêmes pour la plupart des TamagoTux, dans le cas contraire, les méthodes ne sont pas final, elles peuvent donc être override. Il y a trois attributs, le premier, sans doute le plus important, est une liste d’objet Attribute(), le nom est, je pense, assez explicite que pour détailer son rôle, c’est ici que se trouve tous les objets Attribute() de l’animal (faim, fatigue, etc). On a ensuite un objet du type actionThread, qui contient le thread de l’action courante (manger, dormir, jouer,…) et enfin un boolean qui permet de locker l’appel de nouvelle action, le temps que l’action courante se termine. J’ai utilisé un maximum de thread afin de, premièrement, ne pas geler le GUI le temps de l’action, quand le TamagoTux joue, son niveau d’amusement ne grimpe pas en un 10éme de secondes, il monte progressivement, donc toutes les actions et secondement, les Tamagotux eux-même sont threadé, ce qui permet, dans la même partie, de s’occuper de plusieurs Tamagotux en parallèle, de les faire interagir, et cela, dans la même instance de programme. Les méthodes de abstractAnimals() ne sont donc que les implémentations de méthodes de l’interface Animals() que j’ai expliqué plus haut.

Passons maintenant au second package, le package Attribute.

La encore, une interface, une classe abstraite et des classes final. Comme précédement, la plupart des méthodes de l’interface sont implémentées dans la classe abstraite à l’exception de createThreadedAction() car, le thread d’action lié à chaque Attribute(), varie de l’un à l’autre. D’abord un getters et un setters de la value de l’Attribute() . Ils retournent ou prennent un argument un objet Object,() car on en connait pas le type de la valeur de chaque Attribute(). Dans l’implémentation final de chaque Attribute(), un champ static String est défini avec comme valeur, le type de l’Attribute(). Chaque fois que l’on voudra modifier la valeur de l’Attribute(), on vérifia que l’Object() passé en argument est du type de l’Attribute() avant de faire quoique ce soit. Ensuite, on a un boolean qui permet de locker la valeur de l’Attribute(), par exemple, le nom, une fois fixé, on ne peut par renommer notre animal, on va donc rendre l’Attribute() read-only. (C’est aussi utile pour éviter que plusieurs thread ne travaillent en même temps sur le même Attribute() ). Les Attribute() sont identifiés par un String, initialisé dans le constructeur, et permettant de les rendrent accesible via la méthode getAttribute(). L’attribut Name ne peut être défini qu’une seule et unique fois (lors de la construction de l’objet). Les deux méthodes restantes permettent soit de lancer l’action lié a l’Attribute(), soit de la récupérer (pour par exemple, surveiller le thread lié avec un autre thread qui le prendra en argument). Dans la classe abstraite, on retrouve donc un objet Object() qui contient la valeur de l’attribut, son nom, son threadedAction, le type de l’Attribute() (String, Integer, Float, Boolean, etc) et le locker de value. Ensuite, on implémente les méthodes de l’interface, non implémentées dans la classe final, plus, une méthode privée qui vérifie que le type donné en argument lors de la modification de la valeur de l’Attribute() et le type de la valeur de l’Attribute() correspondent. J’ai ici défini plusieurs classes final, correspondant aux Attribute() de base, la fatigue, la faim, le moral, le fun, le nom. On peut en rajouter d’autre, par exemple l’âge, le sexe, la couleur des yeux, le png lié a sa représentation dans le GUI, etc, etc. Il suffit de rajouter une classe, qui implémente les méthodes de l’interface Attribute() (sans forcément dériver de abstractAttribute() ).

Enfin, le dernier package, les actionThread() liés aux Attribute(). Comme pour les deux précédents, une interface, une classe abstraite, des classe final.

On a donc un interface actionThreaded(), trois méthodes, une pour démarrer le thread, une seconde pour le stopper ‘proprement’. La première passe en fait le boolean started à true, le stop à false. Le thread étant un while(starded), contenu dans un while master, par exemple un while(i<modifier), ou i est un itérateur s’incrémentant à chaque iteration et « modifier » le nombre d’iteration à réaliser. Enfin, la méthode run(), qui est l’action réalisée lors que lancement du thread. La classe abstraire abstractThreadedAction() hérite de la classe Thread() de Java. abstractThreadedAction a un champ attribute de type Object(), qui est l’Attribute() sur lequel il va travailler, un modifier, la valeur a rajouter/supprimer/remplacer dans l’Attribute(), un champ time, qui permet de définir combien de temps le thread durera avant de se terminer, un champ sleepTime, qui est le temps en milliseconde de pause à la fin de chaque itération du thread et enfin le boolean sur son état. Les méthodes startThread() et stopThread() sont implémentées dans la classe abstraite et peuvent être override et la méthode run(), quant à elle, est implémentée dans la classe final, afin de changer le comportement du thread selon l’action à réaliser. Là encore, comme pour les Attribute(), le rajout d’action est très simple via l’implémentation de la classe threadedAction() et l’héritage de java.lang.Thread. L’héritage d’abstractThreadedAction() est lui, optionnel car java.lang.Thread implémente tout le nécéssaire au bon fonctionnement du thread.

Au final, on peut donc, en quelques minutes, créer un nouvel animal :

  1. Créer une classe final en implémentant l’interface Animals
  2. Rajouter des attributs a l’animal. Pour en rajouter de nouveaux, il suffit d’implementer Attribute dans une nouvelle classe
  3. Définir un comportement ou une action pour chaque attribut, ou implémenter threadedAction, ensuite, définir la classe créée comme threadedAction de l’Attribute désiré.

Cette conception reste cependant assez simple. Il y a quelques défauts, notamment le fait que, par simplicité, j’ai toutes les méthodes et attributs en public. Evidemment, dans mon code, la plupart des attributs de classe seront privés et seul les méthodes définient dans les interfaces seront public. Mais, voici un exemple « complet » d’utilisation à la fois des interface et de l’heritage pour d’une part placer la réutilisabilité du code au centre de la conception, permettre une certaine modularité dans la conception et donc, faciliter la mise à jour par la suite et au final, malgré une structure interne assez « complexe », fournir au developpeur/end-user, une interface de quelques méthodes permettant facilement d’exploiter l’intégratilité des fonctionnalitées. Sans interface, avec uniquement de l’héritage, la réalisation aurait à la fois été plus complexe à penser, mais aussi à coder et l’utilisabilité final aurait été grandement déteriorée.

Je vais poster le code du programme d’ici quelques jours afin de permettre de voir, en dehors de la « théorie » ou de la modélisation, les résultats et la facilité d’utilisation.

Published by Emmanuel Istace

Musician, Software developer and guitar instructor. https://emmanuelistace.be/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: