-
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
git
et 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 classesSum
etProduct
, il est possible de définir une méthodeString symbol()
qui retourne ce caractère puis de réécrire les méthodes StringasString()
deSum
etProduct
de 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 classesSum
etProduct
. Pour cela, on peut définir les méthodesdouble initialValue()
etdouble cumulativeValue(double accumulator, double value)
dans les classesSum
etProduct
qui retournent les valeurs suivantes :Méthodes\Classes Sum
Product
initialValue
0 1 cumulativeValue
accumulator+value accumulator*value On peut ensuite réécrire la méthode
asValue()
des classesSum
etProduct
de sorte qu’elles utilisent les méthodesinitialValue
etcumulativeValue
.
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 lestokens
d’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 formuleadd2
obtenu par le code suivant := new Product(new Constant(2), new Constant(5)); Formula mult1 = new Sum(new Constant(4), new Constant(7)); Formula add1 = new Product(new Constant(3), add); Formula mult2 = new Sum(mult1, mult2); Formula add2
Le code de la méthode
analyze
créera une pile vide de formules et parcourra lestokens
et appelleraanalyzeToken
sur chacun destokens
. Finalement, le retour de la méthode sera obtenu en dépilant la pile.void analyzeToken(String token, Stack<Formula> stack)
traite unetoken
en appelant une des méthodesanalyzeSum
,analyzeProduct
ouanalyzeDouble
en fonction du type detoken
:"+"
,"*"
ou un double.void analyzeSum(Stack<Formula> stack)
dépile deux éléments de la pile (levant une exception de typeIllegalStateException
si la pile ne contient pas assez d’éléments) afin de créer uneSum
avec ces deux formules puis de l’empiler.void analyzeProduct(Stack<Formula> stack)
dépile deux éléments de la pile (levant une exception de typeIllegalStateException
si la pile ne contient pas assez d’éléments) afin de créer unProduct
avec 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 :
= new Variable("a", 0.1);
Variable a = new Variable("b", 0.1);
Variable b = new Constant(0.1);
Constant c = new Variable("d", 1);
Variable d = new Product(new Sum(new Sum(a,b), c), d); Formula formula
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\times
et la formule est entourée de$
. La formuleformula
devra 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 formuleformula
devra 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 formuleformula
ne s’évalue pas à0.3
lorsqu’on évalue avec lesdoubles
. Une solution pour améliorer la précision des calculs est d’utiliserBigDecimal
pour les opérations. L’objectif est d’obtenir une évaluation à0.3
pourformula
. 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 deBigDecimal
prenant unString
en argument) et utiliser les méthodesadd
etmultiply
deBigDecimal
pourSum
etProduct
.
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
IsConstantVisitor
implémentantFormulaVisitor<Boolean>
qui retourne pour chaque formule si elle correspond à une constante ou pas. Une formule correspond à une constante si elle est de typeConstant
ou bien si elle correspond à un produit ou une somme uniquement composée de constantes.Une classe
IsZeroVisitor
implé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 à0
ou bien si elle correspond à un produit contenant une formule à zéro.Une classe
SimplifyVisitor
implémentantFormulaVisitor<Formula>
qui retourne une formule plus simple qui est équivalente à la formule donnée en paramètre. On considère par exemple la formuleformula
correspondant à l’expression \(((3+3)\times x) + (0\times y)\) et pouvant être obtenue par le code suivant := new Variable("x", 0.1); Variable x = new Constant(3); Constant c1 = new Constant(3); Constant c2 = new Constant(3); Constant zero = new Variable("y", 0.1); Variable y = new Sum(new Product(new Sum(c1,c2), x), new Product(zero, y)); Formula formula
Un appel à
formula.accept(new SimplifyVisitor())
(appliquant le visiteur à la formule) devra retourner une formule équivalente à la formulesimplifiedFormula
correspondant à l’expression \(6\times x\) et pouvant être obtenue par le code suivant := new Product(new Constant(6), x); Formula simplifiedFormula
Une classe
DerivativeVisitor
implémentantFormulaVisitor<Boolean>
, ayant un attributString variableName
initialisé à 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 formuleformula
correspondant à l’expression \((3\times x) + y\) et pouvant être obtenue par le code suivant := new Variable("x", 0.1); Variable x = new Variable("y", 0.1); Variable y = new Sum(new Product(new Constant(3), x), y); Formula formula
Un appel à
formula.accept(DerivativeVisitor("x"))
(appliquant le visiteur à la formule) devra retourner une formule équivalente à la formulederivativeFormula
correspondant à l’expression \(((3\times 1) + (0\times x)) + 0\) et pouvant être obtenue par le code suivant := new Variable("x", 0.1); Variable x = new Product(new Constant(3), new Constant(1)); Formula subFormula1 = new Product(new Constant(0), x); Formula subFormula2 = new Sum(new Sum(subFormula1, subFormula2), new Constant(0)); Formula derivativeFormula
On pourra appeler le visiteur de simplification
derivativeFormula.accept(new SimplifyVisitor())
pour obtenir une formule simplifiéesimplifiedDerivativeFormula
correspondant à l’expression \(3\) et pouvant être obtenue par le code suivant := new Constant(3); Formula simplifiedDerivativeFormula
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
FormulaParser
pour ajouter les opérations correspondant aux nouveaux types de formules.
Autres implémentations de
Formula
On souhaite compléter les formules avec :
- une classe
Maximum
minimum \(\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
,g
eth
, - représentation XML
<maximum> f g h </maximum>
pour trois formulesf
,g
eth
, - représentation latex
\max(f,g,h)
pour trois formulesf
,g
eth
;
- représentation texte
- une classe
Minimum
minimum \(\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
,g
eth
, - représentation XML
<minimum> f g h </minimum>
pour trois formulesf
,g
eth
, - représentation latex
\min(f,g,h)
pour trois formulesf
,g
eth
;
- représentation texte
- une classe
Fraction
fraction \(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
Minus
né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
AbsoluteValue
valeur 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
Square
carré \(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
SquareRoot
carré \(\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
FormulaParser
pour ajouter les opérations correspondant aux nouveaux types de formules.