-
Cours :
- Notions programmation orientée objet et Java (pdf)
- Tests (pdf)
- Principes SOLID (pdf)
- Patrons de conception (pdf)
-
Planches de TD :
- TD 1 (pdf), Corrigé TD 1 (pdf)
- TD 2 (pdf), Corrigé TD 2 (pdf)
- TP 1 (pdf), Corrigé TP 1 (pdf)
Formules
Introduction
Le but de ce projet est d’implémenter des classes pour générer des formules mathématiques. Chaque classe correspondra à un type de formule (constantes, variable, addition, multiplication, …). Le but de ce TP est de travailler les patrons de conception suivants :
- Composite
- Template method
- Strategy
- Abstract factory
- Visitor
Création de projet
Pour la création du dépôt, il vous faut suivre les consignes du TP précédent.
Consignes pour le rendu
Rendu via etulab
Le rendu de votre projet formula est à faire pour le
5 novembre 2024. Il faudra que le dépôt git
sur etulab soit à jour pour le 5 novembre 2024 à 23h59 et
que l’enseignant suivant soit membre du projet avec des droits au moins
égal à reporter : Arnaud Labourel, identifiant
alaboure.
L’ajout de membre se fait via manage \(\rightarrow\) members dans le menu de votre projet sur etulab, puis en cliquant sur le bouton invite members.

Le travail peut être réalisé seul ou en binôme. Dans le cas de binôme, les deux étudiants devront être membres du projet.
Respect de la propriété intellectuelle
Comme pour tout devoir, nous vous demandons de ne pas partager votre programme, complet ou partiel, avec des étudiants n’étant pas membres de votre projet. Tout emprunt que vous effectuez doit être proprement documenté en indiquant quelle partie de votre programme est concernée et de quelle source elle provient (nom d’un autre étudiant, site internet, …). Les emprunts incluent l’utilisation d’IA génératives telles que ChatGPT qui devront donc aussi être documentés.
Fichier README.md du
projet
Votre projet devra contenir à sa racine un fichier
README.md contenant les informations sur ces membres et les
emprunts réalisés. Le format du fichier devra être le suivant :
# Formule
## Membres du projet
- NOM Prénom du premier membre
- NOM Prénom du deuxième membre (si applicable)
## Description des emprunts
- Utilisation de ChatGPT pour les classes : `Main.java`, ...
- ...Critères d’évaluation
Vous serez évalué sur :
- La conception logicielle : votre projet devra dans la mesure du possible respecter les bonnes pratiques de conception logicielle en programmation orientée objet tels que les principes SOLID. Par exemple, des classes ayant trop de responsabilités vous pénaliseront.
- La propreté du code : comme indiqué dans le cours, il est important de programmer proprement. Des répétitions de code trop visibles, des noms mal choisis ou des fonctions ayant beaucoup de lignes de code (plus de dix) vous pénaliseront. Le sujet vous donne les méthodes que vous devez absolument écrire, mais il est tout à fait autoriser d’écrire des méthodes supplémentaires, de créer des constantes, … pour augmenter la lisibilité du code. On rappelle que vous devez écrire le code en anglais.
- La correction du code : on s’attend à ce que votre code soit correct, c’est-à-dire qu’il respecte les spécifications dans le sujet. Comme indiqué dans le sujet, vous devez tester votre code pour vérifier son comportement.
- Les commit/push effectués : il vous faudra
travailler en continu avec
gitet faire des push/commit le plus régulièrement possible. Un projet ayant très peu de pushs/commits effectués juste avant la date limite sera considéré comme suspicieux et noté en conséquence. Un minimum accepté pour le projet sera d’au moins 2 pushs sur deux jours différents et d’au moins 10 commits au total. Dans le cas d’un projet réalisé en binôme, chacun des deux membres du projet devra réaliser au moins un push.
Tâches à réaliser
Interface Formula
La première chose à faire est de définir une interface correspondant
à une formule. Une formule aura deux méthodes, une méthode
asString() renvoyant une chaîne de caractères représentant
la formule et une méthode asValue() renvoyant la valeur de
la formule sous forme d’un double. L’interface devra donc correspondre
au diagramme de classe suivant :
Tâche 1 : Créez l’interface Formula
dans le répertoire src/main/java de votre projet.
Classe Constant
La classe Constant permet de représenter une formule
correspondant à une constante.
Tâche 2 : Créez la classe Constant dans
le répertoire src/main/java de votre projet.
Tâche 3 : Créez une classe de test nommée
ConstantTest dans le répertoire src/test/java
de votre projet qui devra vérifier via des tests unitaires le
comportement de la classe Constant. Vous devez tester les
comportements suivants :
new Constant(3.1).asValue()retourne une valeur égale à3.1;new Constant(3.1).asString()retourne une chaîne de caractères égale à"3.1";
Classe Variable
La classe Variable permet de représenter une formule
correspondant à une variable.
Tâche 4 : Créez la classe Variable dans
le répertoire src/main/java de votre projet.
Tâche 5 : Créez une classe de test nommée
VariableTest dans le répertoire src/test/java
de votre projet qui devra vérifier via des tests unitaires le
comportement de la classe Variable. Vous devez tester les
comportements suivants :
new Variable("X", 3.1).asValue()retourne une valeur égale à3.1;new Variable("X", 3.1).asString()retourne une chaîne de caractères égale à"X";
Classe Sum
La classe Sum permet de représenter une formule
correspondant à une somme d’au moins deux autres formules.
Tâche 6 : Créez la classe Sum dans le
répertoire src/main/java de votre projet.
Tâche 7 : Créez une classe de test nommée
SumTest dans le répertoire src/test/java de
votre projet qui devra vérifier via des tests unitaires le comportement
de la classe Sum. Vous devez tester les comportements
suivants :
new Sum(new Variable("Y", 4.3), new Variable("X", 3.1)).asValue()retourne une valeur proche de7.4;new Sum(new Variable("Y", 4.3), new Variable("X", 3.1), new Variable("Z", 1.1)).asValue()retourne une valeur proche de8.5;new Sum(new Variable("Y", 4.3), new Variable("X", 3.1)).asString()retourne une chaîne de caractères égale à"(Y + X)";new Sum(new Variable("Y", 4.3), new Variable("X", 3.1), new Variable("Z", 1.1)).asString()retourne une chaîne de caractères égale à"(Y + X + Z)"
Classe Product
La classe Product permet de représenter une formule
correspondant à un produit d’au moins deux autres formules.
Tâche 8 : Créez la classe Product dans
le répertoire src/main/java de votre projet.
Tâche 9 : Créez une classe de test nommée
ProductTest dans le répertoire src/test/java
de votre projet qui devra vérifier via des tests unitaires le
comportement de la classe Product. Vous devez tester les
comportements suivants :
new Product(new Variable("Y", 4.3), new Variable("X", 3.1)).asValue()retourne une valeur proche de13.33;new Product(new Variable("Y", 4.3), new Variable("X", 3.1), new Variable("Z", 1.1)).asValue()retourne une valeur proche de14.663;new Product(new Variable("Y", 4.3), new Variable("X", 3.1)).asString()retourne une chaîne de caractères égale à"(Y * X)";new Product(new Variable("Y", 4.3), new Variable("X", 3.1), new Variable("Z", 1.1)).asString()retourne une chaîne de caractères égale à"(Y * X * Z)"
Réécriture code classes
Sum et Product
On peut remarquer que les codes des classes Sum et
Product sont très similaires.
En effet, les attributs et le code des constructeurs sont identiques.
De plus, il est possible de rendre identique les codes des méthodes
String asString() et double asValue() en
effectuant quelques modifications.
Dans la méthode String
asString(), seul le caractère inséré entre les formules est différent. Dans chacune des classesSumetProduct, il est possible de définir une méthodeString symbol()qui retourne ce caractère puis de réécrire les méthodes StringasString()deSumetProductde sorte qu’elles utilisent la méthodeString symbol().De même, on peut faire que le code de la méthode
asValue()soit le même dans les classesSumetProduct. Pour cela, on peut définir les méthodesdouble initialValue()etdouble cumulativeValue(double accumulator, double value)dans les classesSumetProductqui retournent les valeurs suivantes :Méthodes\Classes SumProductinitialValue0 1 cumulativeValueaccumulator+value accumulator*value On peut ensuite réécrire la méthode
asValue()des classesSumetProductde sorte qu’elles utilisent les méthodesinitialValueetcumulativeValue.
Réécriture par extension
On va commencer par appliquer le patron de conception template
method (patron de méthode). On va donc créer une classe abstraite
VariadicOperator contenant le code en commun entre
Sum et Product. La classe
VariadicOperator aura trois méthodes abstraites :
intialValue, cumulativeValue et
symbol. Les nouvelles classes Sum et
Product étendront la classe VariadicOperator
en implémentant ces trois méthodes abstraites. On aura donc la
configuration suivante :
Tâche 10 : Créez un package
extension dans le répertoire src/main/java de
votre projet.
Tâche 11 : Remplacez les anciennes classes
Sum et Product par les nouvelles classes
Sum et Product que vous mettrez dans le
package extension de votre projet. Vérifiez que
les tests des classes passent toujours.
Réécriture par délégation
Pour cette autre façon de factoriser le code entre les classes
Sum et Product, on va créer une nouvelle
classe VariadicOperator dans un nouveau package
strategy. Cette nouvelle classe délèguera les parties
spécifiques de l’évaluation et de représentation en chaîne de caractères
à une interface Operator. L’interface Operator
contiendra les trois méthodes symbol,
initialValue et cumulativeValue et sera
implémenté par deux classes Addition et
Multiplication. On aura donc le diagramme de classes
suivant :
Tâche 12 : Créez un package
strategy dans le répertoire src/main/java de
votre projet.
Tâche 13 : Créez les classes Addition,
Multiplication et l’interface Operator dans le
package strategy de votre projet.
Tâche 14 : Créer une nouvelle classe
VariadicOperator dans le package
extension (sans supprimer la classe
VariadicOperator du package extension) de
votre projet.
Tâche 15 : Créer des classes
VariadicOperatorTest, AdditionTest et
MutiplicationTest dans le répertoire
src/test/java de votre projet qui devront vérifier via des
tests unitaires le comportement des classes
VariadicOperator, Addition et
Mutiplication.
Parseur formule notation postfixée
Nous souhaitons écrire un programme qui prend en paramètre une
expression en notation postfixée composée de nombres (constantes), de
+ et de * et qui calcule la valeur de
l’expression. En notation postfixée (ou polonaise inversée), on place
l’opérateur à droite des deux opérandes (on supposera que les opérations
+ et * sont binaires et ne s’appliquent donc
que sur les deux derniers termes). Par exemple, l’expression
(2 * 5) + 3 * (4 + 7) s’écrit en notation postfixée
2 5 * 3 4 7 + * + (les parenthèses sont inutiles).
Le but est donc de créer une classe FormulaParser qui va
créer une formule (interface Formula) à partir d’une chaîne
de caractère. Cette classe FormulaParser correspondra au
diagramme suivant :
Les méthodes de FormulaParser ont le comportement
suivant :
Formula analyze(String... tokens)analyse lestokensd’une expression postfixée et retourne une formule correspondant à l’expression. Par exemple, un appel àanalyze("2", "5", "*", "3", "4", "7", "+", "*", "+")devra renvoyer une formule équivalente à la formuleadd2obtenu par le code suivant :Formula mult1 = new Product(new Constant(2), new Constant(5)); Formula add1 = new Sum(new Constant(4), new Constant(7)); Formula mult2 = new Product(new Constant(3), add); Formula add2 = new Sum(mult1, mult2);Le code de la méthode
analyzecréera une pile vide de formules et parcourra lestokenset appelleraanalyzeTokensur chacun destokens. Finalement, le retour de la méthode sera obtenu en dépilant la pile.void analyzeToken(String token, Stack<Formula> stack)traite unetokenen appelant une des méthodesanalyzeSum,analyzeProductouanalyzeDoubleen fonction du type detoken:"+","*"ou un double.void analyzeSum(Stack<Formula> stack)dépile deux éléments de la pile (levant une exception de typeIllegalStateExceptionsi la pile ne contient pas assez d’éléments) afin de créer uneSumavec ces deux formules puis de l’empiler.void analyzeProduct(Stack<Formula> stack)dépile deux éléments de la pile (levant une exception de typeIllegalStateExceptionsi la pile ne contient pas assez d’éléments) afin de créer unProductavec ces deux formules puis de l’empiler.void analyzeDouble(String doubleToken, Stack<Formula> stack)empile une constante ayant la valeur dutoken.
Tâche 16 : Créer une nouvelle classe
FormulaParser dans le répertoire src/main/java
de votre projet.
Tâche 17 : Créer la classe
FormulaParserTest dans le répertoire
src/test/java de votre projet qui devront vérifier via des
tests unitaires le comportement de la classe FormulaParser.
Pour tester certains comportements, vous aurez besoin de redéfinir la
méthode equals (déjà défini dans Object) dans
les classes Sum, Product et
Constant afin de considérer égales les sommes (ou les
produits) sommant des formules égales et les constantes de valeur
égale.
Réécriture avec une fabrique abstraite
Si on souhaite utiliser la classe VariadicOperator du
package strategy à la place des classes
Sum et Product du package
extension, on doit modifier à plusieurs endroits le code de
FormulaParser. Ce n’est pas satisfaisant et on va donc
créer une fabrique abstraite et changer le code de
FormulaParser pour utiliser la fabrique abstraite pour
construire les formules. Le diagramme de classes sera le suivant :
Tâche 18 : Créez l’interface
FormulaFactory et les deux classes
StrategyFormulaFactory et
ExtensionFormulaFactory qui l’implémentent dans le
répertoire src/main/java de votre projet
Tâche 19 : Modifiez le code de la classe
FormulaParser pour qu’elle utilise une
FormulaFactory pour créer les formules.
Utilisation du patron visitor
On souhaite utiliser le patron de conception visitor pour
les classes implémentant Formula. Afin d’illustrer le
comportement de ces trois visiteurs on considérera la formule
formula construite grâce au code suivant :
Variable a = new Variable("a", 0.1);
Variable b = new Variable("b", 0.1);
Constant c = new Constant(0.1);
Variable d = new Variable("d", 1);
Formula formula = new Product(new Sum(new Sum(a,b), c), d);On souhaite définir 3 types de visiteurs :
LatexVisitor: produit un texte LaTeX représentant la formule. Le symbole de l’addition est le+alors que la multiplication a pour symbole\timeset la formule est entourée de$. La formuleformuladevra avoir la représentation LaTeX suivante :$(((a + b) + 0.1) \times d)$.XMLVisitor: produit un texte XML représentant la formule. On utilisera des balises<product>,<sum>,<variable>et<constant>correspondant aux différents types de formules. Pour les variables, on utilisera des balises<name>et<value>pour les attributs. La formuleformuladevra avoir la représentation XML suivante :<product> <sum> <sum> <variable> <name>a</name> <value>0.1</value> </variable> <variable> <name>b</name> <value>0.1</value> </variable> </sum> <constant>0.1</constant> </sum> <variable> <name>d</name> <value>1.0</value> </variable> </product>PreciseEvaluatorVisitor: évalue la valeur de la formule en utilisantBigDecimal. Les calculs avec les doubles contiennent des imprécisions et par exemple la formuleformulane s’évalue pas à0.3lorsqu’on évalue avec lesdoubles. Une solution pour améliorer la précision des calculs est d’utiliserBigDecimalpour les opérations. L’objectif est d’obtenir une évaluation à0.3pourformula. Pour cela, il vous faudra créer lesBigDecimalà partir des chaînes de caractères des valeurs des constantes et des variables (en utilisant le constructeur deBigDecimalprenant unStringen argument) et utiliser les méthodesaddetmultiplydeBigDecimalpourSumetProduct.
Le diagramme de classe sera le suivant :
Tâche 20 : Créez l’interface
FormulaVisitor<R>.
Tâche 21 : Créez l’interface
VisitableFormula qui étends l’interface
Formula et définit une méthode
<R> R accept(FormulaVisitor<R> visitor).
Tâche 22 : Modifiez les classes
Variable et Constant pour qu’elles
implémentent VisitableFormula au lieu de
Formula et qu’elles définissent une méthode
accept.
Tâche 23 : Modifiez les classes
VariadicOperator, Sum et Product
du package extension pour que VariadicOperator
implémente VisitableFormula et que les classes
Sum et Product définissent une méthode
accept. Ajouter aussi des méthodes
VisitableFormula[] getFormulas() dans Sum et
Product afin que les visiteurs puissent avoir l’accès aux
données des classes.
Tâche 24 : Créez la classe XMLVisitor
implémentant FormulaVisitor<String> dans le
répertoire src/main/java de votre projet.
Tâche 25 : Créez une classe de test nommée
XMLVisitorTest dans le répertoire
src/test/java de votre projet qui devra vérifier via des
tests unitaires le comportement de la classe
XMLVisitor.
Tâche 26 : Créez la classe
PreciseEvaluatorVisitor implémentant
FormulaVisitor<BigDecimal> dans le répertoire
src/main/java de votre projet.
Tâche 27 : Créez une classe de test nommée
PreciseEvaluatorVisitorTest dans le répertoire
src/test/java de votre projet qui devra vérifier via des
tests unitaires le comportement de la classe
PreciseEvaluatorVisitor.
Tâche 28 : Créez la classe LatexVisitor
implémentant FormulaVisitor<String>dans le répertoire
src/main/java de votre projet.
Tâche 29 : Créez une classe de test nommée
LatexVisitorTest dans le répertoire
src/test/java de votre projet qui devra vérifier via des
tests unitaires le comportement de la classe LatexVisitor.
Tâches supplémentaires
Les tâches décrites ci-dessous (tâches 30 et 31) peuvent être réalisée dans l’ordre de votre choix.
Visiteurs supplémentaires
Pour les fonctionnalités suivantes, on supposera que les formules sont uniquement composé de Variables, Constantes, Sommes et Produits. On souhaiterait avoir les classes suivantes :
Une classe
IsConstantVisitorimplémentantFormulaVisitor<Boolean>qui retourne pour chaque formule si elle correspond à une constante ou pas. Une formule correspond à une constante si elle est de typeConstantou bien si elle correspond à un produit ou une somme uniquement composée de constantes.Une classe
IsZeroVisitorimplémentantFormulaVisitor<Boolean>qui retourne pour chaque formule si elle est toujours égale à 0 ou pas. Une formule est égale à zéro si elle est constante et s’évalue à0ou bien si elle correspond à un produit contenant une formule à zéro.Une classe
SimplifyVisitorimplémentantFormulaVisitor<Formula>qui retourne une formule plus simple qui est équivalente à la formule donnée en paramètre. On considère par exemple la formuleformulacorrespondant à l’expression \(((3+3)\times x) + (0\times y)\) et pouvant être obtenue par le code suivant :Variable x = new Variable("x", 0.1); Constant c1 = new Constant(3); Constant c2 = new Constant(3); Constant zero = new Constant(3); Variable y = new Variable("y", 0.1); Formula formula = new Sum(new Product(new Sum(c1,c2), x), new Product(zero, y));Un appel à
formula.accept(new SimplifyVisitor())(appliquant le visiteur à la formule) devra retourner une formule équivalente à la formulesimplifiedFormulacorrespondant à l’expression \(6\times x\) et pouvant être obtenue par le code suivant :Formula simplifiedFormula = new Product(new Constant(6), x);Une classe
DerivativeVisitorimplémentantFormulaVisitor<Boolean>, ayant un attributString variableNameinitialisé à la construction via un argument du constructeur de même nom et type, qui retourne une formule correspondant à la dérivée de la formule par rapport à la variable de nomvariableName. On considère par exemple la formuleformulacorrespondant à l’expression \((3\times x) + y\) et pouvant être obtenue par le code suivant :Variable x = new Variable("x", 0.1); Variable y = new Variable("y", 0.1); Formula formula = new Sum(new Product(new Constant(3), x), y);Un appel à
formula.accept(DerivativeVisitor("x"))(appliquant le visiteur à la formule) devra retourner une formule équivalente à la formulederivativeFormulacorrespondant à l’expression \(((3\times 1) + (0\times x)) + 0\) et pouvant être obtenue par le code suivant :Variable x = new Variable("x", 0.1); Formula subFormula1 = new Product(new Constant(3), new Constant(1)); Formula subFormula2 = new Product(new Constant(0), x); Formula derivativeFormula = new Sum(new Sum(subFormula1, subFormula2), new Constant(0));On pourra appeler le visiteur de simplification
derivativeFormula.accept(new SimplifyVisitor())pour obtenir une formule simplifiéesimplifiedDerivativeFormulacorrespondant à l’expression \(3\) et pouvant être obtenue par le code suivant :Formula simplifiedDerivativeFormula = new Constant(3);
Tâche 30 : Créez les classes décrites ci-dessus. Vous devez :
- Tester les classes que vous créez ;
- Faire attention à minimiser la duplication de code ;
- Faire attention à respecter les principes SOLID ;
- Ajouter les classes dans les fabriques abstraites ;
- Ajouter les classes aux différents visiteurs ;
- Modifier
FormulaParserpour ajouter les opérations correspondant aux nouveaux types de formules.
Autres implémentations de
Formula
On souhaite compléter les formules avec :
- une classe
Maximumminimum \(\max(f_1, f_2, \dots, f_k)\) pour des formules \(f_1, f_2, \dots ,f_k\) :- représentation texte
max(f,g,h)pour trois formulesf,geth, - représentation XML
<maximum> f g h </maximum>pour trois formulesf,geth, - représentation latex
\max(f,g,h)pour trois formulesf,geth;
- représentation texte
- une classe
Minimumminimum \(\min(f_1, f_2, \dots, f_k)\) pour des formules \(f_1, f_2, \dots ,f_k\) :- représentation texte
min(f,g,h)pour trois formulesf,geth, - représentation XML
<minimum> f g h </minimum>pour trois formulesf,geth, - représentation latex
\min(f,g,h)pour trois formulesf,geth;
- représentation texte
- une classe
Fractionfraction \(f/g\) pour deux formules \(f\) et \(g\) :représentation texte
f/g,représentation XML
<fraction> <numerator>f</numerator> <denominator>g</denominator> </fraction>représentation latex
\frac{f}{g};
- une classe
Minusnégation \(-f\) pour une formule \(f\) :- représentation texte
-f, - représentation XML
<minus>f</minus>, - représentation latex
-f;
- représentation texte
- une classe
AbsoluteValuevaleur absolue \(|f|\) pour une formule \(f\) :- représentation texte
|f|, - représentation XML
<absoluteValue>f</absoluteValue>, - représentation latex
\lvert f \rvert;
- représentation texte
- une classe
Squarecarré \(f^2\) pour une formule \(f\) :- représentation texte
f², - représentation XML
<square>f</square>, - représentation latex
f^2;
- représentation texte
- une classe
SquareRootcarré \(\sqrt{f}\) pour une formule \(f\)- représentation texte
sqrt(f), - représentation XML
<squareRoot>f</squareRoot>, - représentation latex
\sqrt{f};
- représentation texte
- une classe
Power: puissance \(f^g\) pour deux formules \(f\) et \(g\)- représentation texte
f^k; - représentation
XML
<power><base>f</base><exponent>g</exponent></power>, - représentation latex
f^{g}.
- représentation texte
Tâche 31 : Créez les classes décrites ci-dessus. Vous devez :
- Tester les classes que vous créez ;
- Faire attention à minimiser la duplication de code ;
- Faire attention à respecter les principes SOLID ;
- Ajouter les classes dans les fabriques abstraites ;
- Ajouter les classes aux différents visiteurs ;
- Modifier
FormulaParserpour ajouter les opérations correspondant aux nouveaux types de formules.