Dans tout l'exercice, le switch sur les classes introduit en Java 17 est interdit sauf mention explicite du contraire.
Le Simple Time Protocol (STP) est un protocole qui fournit une liste de commandes pour manipuler des chronomètres:
La compagnie EvilCorp (pour qui vous travaillez) fournit une librairie qui parse les commandes du Simple Time Protocol (STP). La librairie qui peut être récupérée ici définit une interface STPCommand
commune à toutes les commandes et offre une classe pour représenter chacune des quatre commandes. La classe
STPParser
offre une static factory method permettant de convertir une chaîne de caractères en l'objet STPCommand
si c'est possible:
public static Optional<STPCommand> parse(String cmd);
La librairie a été mal conçue. Le but serait que cette librairie soit utilisable dans plusieurs applications qui utilisent le protocole Simple Time Protocol (STP) pour des usages différents.
Pour comprendre le problème, écrivez une application Triviale
qui utilise la librairie pour parser les commandes lues au clavier et qui affiche "Pas compris" lorsqu'elle lit une ligne non reconnue par le parser de la librairie, "Au revoir" lorsqu'elle rencontre la commande hello et "non implémenté" pour toutes les autres commandes.
Quel principe SOLID n'est pas implémenté par la librairie stp
?
Comment pourrait-on résoudre le problème en utilisant le polymorphisme ? Est-ce que c'est envisageable dans cette situation ?
Quel patron permet d'ajouter des nouvelles opérations aux classes implémentant l'interface STPCommand
sans modifier ces classes ?
Modifiez la librairie fournie par EvilCorp pour la rendre utilisable avec ce patron et récrivez la petite application Triviale
demandée précédemment.
Modifiez la classe Application.java
, qui implémente une gestion de chronomètres en local, pour utiliser cette nouvelle librairie.
Faites un version moderne de la libaire STP dans un nouveau package com.evilcorp.stphipster
qui utilise toutes les fonctionnalités de Java 17. Les classes des commandes seront des record
et l'interface STPCommand sera sealed
(votre interface devait déjà être sealed même si vous n'utilisez pas les switchs). Réécrivez la classe Application
dans une classe ApplicationHipster
en utilisant les switchs de Java 17.
A faire chez vous: Très content de votre travail, votre patron vous demande d'étendre l'application pour qu'elle:
Il est fort probable que vous ayez à écrire de nombreuses extensions de ce type, donc votre patron vous demande une architecture logicielle facilement extensible.
Modifiez votre application pour permettre l'ajout de ces fonctionnalités et ajoutez les deux fonctionnalités demandées. Vous continuerez à utiliser votre classe Aplication qui utilise des visiteurs et pas la classe de la question précédente.
Le but de cet exercice est de modifier un code existant permettant d'effectuer le calcul d'une expression en notation polonaise inverse dans le but de fermer la hiérarchie d'expressions tout en laissant la possibilité à un utilisateur d'écrire ses propres traitements.
Une expression arithmétique sera représenté par objet de l'interface Expr
qui est téléchargeable
ici. Cette interface scellée autorise des records Value
représentant
des valeurs entières et des records BinOp
qui permettent de représenter des opérations binaires quelconques.
Le code suivant permet d'évaluer une expression en polonaise inverse:
var iterator = Pattern.compile(" ").splitAsStream("+ * 4 + 1 1 + 2 3").iterator(); var expr = Expr.parse(iterator); System.out.println(expr); System.out.println(expr.eval());
Dessinez l'arbre d'expressions créé par le code ci-dessus puis
dessinez sur l'arbre l'évaluation de l'expression (c'est-à-dire un appel à la méthode eval()
).
Modifiez le code pour introduire le visiteur ExprVisitor
,
comme déclaré ci-dessous:
public interface ExprVisitor { public int visitValue(Value value); public int visitBinOp(BinOp binOp); }
Ecrivez une classe EvalExprVisitor
qui effectue l'évaluation en utilisant
le patron visitor plutôt qu'en utilisant la méthode eval
.
Une fois que l'EvalExprVisitor
fonctionne, supprimez la méthode eval
de l'interface Expr
(et des sous classes).
On souhaite faire la même transformation mais avec la méthode toString
à la place
de la méthode eval
. Comme toString
retourne une String et pas un int
comme eval
, il faut faire évoluer l'interface ExprVisitor
.
Après avoir modifié votre interface ExprVisitor, écrivez une classe ToStringVisitor
qui effectue le même travail que toString
puis retirez les méthodes toString
une fois que le visiteur marche.
Le toStringVisitor
que vous avez écrit (comme les méthodes toString
qu'il
y avait précédemment) n'est pas très efficace car il y a beaucoup de créations
de String
intermédiaires; il serait préférable d'utiliser un seul StringBuilder
pour tout le calcul.
Modifiez le ToStringVisitor
pour n'utiliser qu'un seul StringBuilder
.
Note: cela nécessite peut-être de modifier l'interface ExprVisitor
, ou pas,
il y a plusieurs solutions.
Revenons sur ce qu'est un visiteur: un visiteur est un objet qui associe à
une classe (Value
ou BinOp
) un code à exécuter.
Il peut donc être implanté avec une table de hachage qui associe à une classe
(un objet de type java.lang.Class
) une lambda contenant le code
à exécuter.
On se propose d'écrire la classe ExprVisitor
de telle sorte ce que
le code suivant fonctionne:
ExprVisitor<Integer> evalVisitor = new ExprVisitor<>(); evalVisitor .when(Value.class, value -> { ... }) .when(BinOp.class, binOp -> { ... }); evalVisitor.visit(expr);
Quelle doit être la signature de la méthode when
?
Quelle est la signature de la méthode visit
? Quelle structure de données doit être utilisée pour implanter les méthodes when
et visit
?
Ecrire le code de la classe ExprVisitor
.
Implémentez les visiteurs correspondant aux méthodes eval
et toString
.
Dans l'exercice précédent, on a réalisé le visiteur pour la méthode toString()
en utilisant un seul StringBuilder
. Comment adapter la classe ExprVisitor
pour pouvoir faire de même ici ?
Réalisez un visiteur utilisant un seul StringBuilder
calculant la méthode toString
.
Quels sont les avantages et inconvénients d'écrire le code du visiteur de cette façon là par rapport au visiteur classique du GOF ou à utiliser un switch de Java 17?
Le but de l'exercice est créer un petite librairie permettant d'accéder à un sytème de fichier. L'API Java contient déjà de nombreuses classes remplissant ce rôle, il ne s'agit donc vraiment que d'un exercice.
Dans un premier temps, on veut faire un librairie pour Java 23.
Ecrivez une hiérarchie de classes permettant de représenter un système de fichier contenant à la fois des fichiers et des répertoires. Pour les fichiers, on donnera le Path
, le nom du fichier et l'extension et pour les répertoires, on donnera le Path
, le nom du répertoire et son contenu. Quel design pattern avez-vous utilisé ?
Complétez votre code pour pouvoir construire un objet de votre hiérarchie à partir d'un Path
avec un méthode static of(Path path)
.
Vous implanterez votre librairie avec la méthode Files.list(Path path)
qui renvoie un Stream<Path>
contenant les éléments du répertoire.
Dans une classe Application
, écrivez une méthode static findFilesWithExtension(Path directory, String extension)
qui renvoie la liste des noms des tous les fichiers ayant une extension donnée en utilisant votre librairie.
Le problème de votre librairie est qu'elle construit directement un énorme objet même si l'objet n'est pas utilisé. On voudrait rajouter un objet construit de façon lazy : le contenu du répertoire n'est calculé que si on y accède. Rajoutez une méthode static ofLazy(Path path)
. Attention, le code qui était écrit pour la version précédente de la librairie doit continuer à fonctionner. L'utilisateur ne doit voir aucune différence entre les versions lazy et non-lazy outre l'appel ofLazy
.
On veut maintenant une version de la librairie utilisable sans le switch sur les interfaces sealed. Modifiez le code de votre librairie et la classe Application
.
Idéalement, on voudrait ne pas créer de structure intermédiaire représentant le système de fichier car cet ensemble d'objet peut être énorme. C'est ce que permet FileVisitor. Après avoir compris cette classe, quels sont les inconvénients de cette approche.