Noms des étudiants du binôme :
- LAQUEUVRE Damien
- MOULON Florent
Le patron de conception utilisé ici est un patron composite, L'interface composante est l'interface
MobileObject
, la feuille est la classeWheel
et les composites sont tous lesVehicle
. En effet, ces deux dernières classes imlémententMobileObject
, la classeSimpleBike
, hérite deBike
elle même héritant deVehicle
qui possède des roues.
Pour la classe
TagAlongBike
, elle hérite deSimpleBike
et à un attributSimpleBike
correspondant au vélo de l'enfant. Nous n'avons rien à changer sur les algorithmes, car les fonctions calculent déjà leur valeur en prennant en compte chacunes des composantes (en sommant pour la masse et donc pour le calcul de la vitesse).
La méthode
getVelocity()
utilise le patron de conception itérateur pour parcourir les composants d'un véhicule. L'avantage de ce patron est qu'il permet de parcourir une collection d'objets sans exposer sa structure interne. Ainsi tant qu'une class implémente l'interfaceIterable
, on peut la parcourir avec un itérateur et donc l'utiliser dans la méthodegetVelocity()
sans avoir à modifier son implémentation.On change la structure de donnée utilisée pour stocker les composants d'un
Vehicule
- de :
protected final Set<MobileObject> components = new HashSet<>();
- à :
protected final List<MobileObject> components = new ArrayList<>();
Nous n'avons pas besoin de modifier l'implementation de la méthode
getVelocity()
car elle utilise déjà un itérateur pour parcourir les composants du véhicule. Elle est donc indépendante de la structure de donnée utilisée pour stocker les composants puisques les deux classes (Set
etList
) implémentent l'interfaceIterable
.
L'objectif du pattern singleton est de toujours avoir une seule instance maximum de la classe. Pour construire un pattern singleton nous avons écrit le code suivant. Nous pouvons dérouler comment se passe un appel. Ici lors d'un appel
getTime()
(via un appel de classe et pas d'instance, car la fonction eststatic
) celle-ci vérifie si une instance de classe existe déjà, si non elle en créé une nouvelle avant de renvoyer le temps.
public final class Clock {
private static Clock c;
private final int time = new Random().nextInt(25);
// On créé un constructeur privé, qui est accessible seulement à l'intérieur de
// la classe. Le singleton est le seul à pouvoir s'instancier (depuis getTime())
private Clock() {
super();
}
/**
* Random integer between 0 and 24 inclusive.
*/
public final static int getTime() {
// On vérifie si un instance de Clock à déjà été créé
if (Clock.c == null) {
// Si ce n'est pas le cas nous la créons
Clock.c = new Clock();
}
// Puis on return time
return c.time;
}
}
Les classes Bike et Wheel, appartiennent-elles au même paquetage[^3] ? Quel type de dépendance y a-t-il entre les deux classes ? Cette dépendance adhère-t-elle aux bonnes pratiques de conception ?
Les classes
Bike
etWheel
n'appartiennent pas au même paquetage. En effet :
Bike
appartient au paquetfr.polytech.sim.cycling
- alors que
Wheel
appartient au paquetfr.polytech.sim.transport
La dépendance entre les deux classes est une dépendance de composition cyclique. En effet,
Wheel
a une référence versBike
pour obtenir l'effort appliqué sur la roue. EtBike
a une référence versWheel
pour pouvoir calculer la masse du vélo.Cette dépendance n'adhère pas aux bonnes pratiques de conception car elle crée une forte dépendance (interdépendance) entre les deux classes.
Quelle fonctionnalité de la classe Bike utilise la classe Wheel ? Y a-t-il déjà une abstraction de la classe Bike qui isole cette fonctionnalité ? Dans quel paquetage se trouve cette abstraction ?
La classe
Bike
utilise la méthodegetMass()
etgetVelocity()
de la classeWheel
pour calculer la masse du vélo. Cette fonctionalité est abstraite dans la classe abstraiteVehicule
qui se trouve dans le paquetagefr.polytech.sim.transport
.
Proposez, et réalisez, une solution pour casser la dépendance cyclique entre les classes Bike et Wheel.
Pour casser la dépendance cyclique entre les classes
Bike
etWheel
, nous allons utiliser une abstraction de la fonctionalitégetPush()
deBike
(qui est utilisée parWheel
) que nous mettrons dans le paquetagetransport
. Ainsi la classeBike
sera dépendante du paquettransport
maisWheel
ne sera plus dépendante du paquetcycling
.Par chance, cette abstraction existe déjà dans la classe abstraite
Vehicule
qui se trouve dans le paquetagefr.polytech.sim.transport
. Il nous suffit donc de mofiier la variabledrive
deWheel
pour qu'elle soit de typeVehicule
et non plus de typeBike
. Ce changement suffit carVehicule
contient déjà la méthodegetPush()
qui est utilisée parWheel
etBike
implémente déjà cette méthode.
On utilise le patron de conception patron de méthode pour centraliser l'étape commune des 2 classes à un seul endroit. Pour ce faire, on découpe la méthode
log()
en 2 méthodes :construction_du_log()
etecriture_du_log()
.La méthode
log()
devient alors notre patron de méthode qui appelle les 2 méthodesconstruction_du_log()
etecriture_du_log()
: L'étape commune de fabrication du message final est donc centralisée dans la méthodeconstruction_du_log()
et implémentée directement dans la classe abstraiteNamedLogger
.La méthode
ecriture_du_log()
est quant à elle abstraite et doit être implémentée par les classes filles. On remplace donc la méthodelog()
des classesFileLogger
etConsoleLogger
par la méthodeecriture_du_log()
.public void log(String format, Object... args) { String message = this.construction_du_log(format); this.ecriture_du_log(message); } protected String construction_du_log(String format, Object... args) { String entry = String.format(format, args); String message = String.format("%s\t%s\n", this.name, entry); return message; } abstract protected void ecriture_du_log(String message);
Nous centralisons dans l'interface
Logger
une méthode statiquegetLogger()
qui définit le choix de la réalisation de log.
static Logger getLogger(String name) {
return new ConsoleLogger(name);
}
La différence avec le pattern Singleton est que, si de la même manière nous avons une méthode statique à laquelle nous faisons appel, notre pattern instancie autant de classe qu'il existe d'appel. Comme dans notre cas nous n'avons qu'un seul décorateur nous n'avons pas créé de décorateur abstrait pour avoir du polymorphisme.
Nous créons la classe suivante :
package fr.polytech.sim.log;
public class TimestampedLoggerDecorator implements Logger {
private Logger logger;
protected TimestampedLoggerDecorator(Logger logger) {
this.logger = logger;
}
@Override
public void log(String format, Object... args) {
logger.log(java.time.LocalDateTime.now() + " : " + format, args);
}
}
Puis nous modifions notre appel dans le logger pour intégrer le décorateur :
static Logger getLogger(String name) {
Logger l = new ConsoleLogger(name);
return new TimestampedLoggerDecorator(l);
}
Cette fonctionalité marche peut importe le type de
logger
(console ou file) car elles implémentent toutes l'interfacelogger
, qui est la classe à laquelle nous faisons appel.
La classe Context adapte l'outil
ServiceLoader
grâce au patron de conception adaptateur avec délégation.
Utilisez la classe utilitaire Context pour injecter un objet de type Bike dans la simulation, au lieu de l'instancier avec le mot clef new. Changez la classe injectée de SimpleBike à TagAlongBike.
Dans la classe
BikeSimulator
, on utilise la classeContext
pour injecter un objet de typeBike
dans la simulation :Context ctx = new Context(); Bike bike = ctx.inject(Bike.class);Puis on modifie dans le fichier
fr.polytech.sim.cycling.Bike
la classe pour mettre la classeTagAlongBike
à la place deSimpleBike
.
Peut-on avoir plusieurs lignes dans le fichier fr.polytech.sim.cycling.Bike ? À quoi correspond chaque de ces lignes ?
Oui, on peut avoir plusieurs lignes dans le fichier
META-INF/services/fr.polytech.sim.cycling.Bike
. Les différentes lignes font référence à différentes implémentations de l'interfaceBike
. Lorsque leServiceLoader
est utilisé pour charger les services, il retourne un itérateur sur toutes les classes listées dans le fichier.
Observez le type de retour de la méthode injectAll(). Quel patron de conception propose cette méthode pour parcourir tous les objets d'un type donné disponibles dans le contexte applicatif ?
La méthode
injectAll(Class<T> klass)
retourne unIterator<T>
, elle propose donc le patron de conception itérateur pour parcourir tous les objets d'un type donné disponibles dans le contexte applicatif.
Réaliser cette méthode en cohérence avec son objectif décrit ci-dessus et détaillé dans sa documentation.
Il nous suffit d'ajouter une nouvelle ligne contenant
fr.polytech.sim.cycling.TagAlongBike
au fichierMETA-INF/services/fr.polytech.sim.cycling.Bike
qui contient donc :fr.polytech.sim.cycling.SimpleBike fr.polytech.sim.cycling.TagAlongBike
Modifiez la simulation de vélo pour utiliser cette même méthode afin de simuler tous les types de vélo presénts dans la configuration. Modifiez la configuration pour simuler et SimpleBike et TagAlongBike.
On modifie la simulation de vélo pour utiliser la méthode
injectAll()
afin de simuler tous les types de vélo présents dans la configuration.On ajoute donc le code suivant dans
BikeSimulator
:Iterator<Bike> bikes = Context.injectAll(Bike.class); while (bikes.hasNext()) { Bike bike = bikes.next(); System.out.println("Simulating " + bike.getClass().getSimpleName()); this.logger.log("Bike's speed %.2f Km/h.", bike.getVelocity()); this.logger.log("Bike's mass %.2f Kg.", bike.getMass()); }Il a également fallu coder la méthode
injectAll()
dans la classeContext
:public static <T> Iterator<T> injectAll(Class<T> klass) { ServiceLoader<T> serviceLoader = ServiceLoader.load(klass); return serviceLoader.iterator(); }