Corrigé de l’Examen Design Pattern 2010-2011
1 - Quelques questions de réflexion (5 points)
Le savoir-faire d’une classe est défini par son interface : ensemble des prototypages de toutes les méthodes publiques. On peut avoir deux classes dont l’intersection de leur interface est non vide.
En s’inspirant de cette remarque, l’intérêt pour une classe de définir plusieurs sous-interfaces, qui sont des sous-ensembles de son interface, réside dans le fait qu’une classe implémentant plusieurs interfaces acquiert des identités différentes. Cela permet à d’autres classes de n’implémenter que certaines interfaces, selon leurs besoins, et ainsi de limiter les méthodes à celles qui leur sont utiles. Cette approche favorise aussi la réutilisabilité du code.
Question 2) (1 point)
Les deux principaux patterns créateurs sont les patterns Méthode de Fabrique et Fabrique abstraite. On note souvent une confusion entre ces deux patterns.
Le lien entre ces deux patterns est que le pattern Fabrique abstraite est une généralisation du pattern Méthode de Fabrique. Il s’agit d’un ensemble de Méthodes de Fabrique pour des classes appartenant à la même famille.
Question 3) (1 point)
Dans un développement logiciel, on cherche à séparer la création des objets et leur utilisation.
Cette séparation est essentielle pour réduire les coûts de maintenance et de modification du code. En isolant la logique de construction (par exemple, via une Méthode de Fabrique) de l’utilisation des objets, on garantit que des changements dans la création ou l’interface d’un objet n’impacteront pas les autres parties du programme.
Question 4) (1 point)
Pour décrire un pattern comportemental, on utilise plus fréquemment des diagrammes de séquences, tandis que pour un pattern structurel, on privilégie les diagrammes de classes.
Dans un pattern comportemental, l’accent est mis sur les interactions et les comportements entre objets, ce qui justifie l’utilisation de diagrammes de séquences. Un pattern structurel, en revanche, propose une solution statique et précise en termes de relations entre classes (héritage, association), ce qui rend le diagramme de classes plus adapté.
Question 5) (1 point)
Quelles sont les bonnes questions à se poser pour choisir entre définir une interface ou une classe abstraite ?
1) Est-ce que j’ai un ou plusieurs attributs communs à mes classes filles ? Si oui, une classe abstraite est le meilleur choix.
2) Est-ce que j’ai une ou plusieurs méthodes dont l’implémentation est commune à toutes les classes filles ? Si oui, une classe abstraite est préférable.
2 - Quelques petits exemples à décrypter (4,5 points)
Question 1) (1,5 point)
Dans un programme de création de plans de pièces avec leurs meubles, plusieurs modes d’affichage d’un même plan existent : le plan de base, le plan avec mesures, le plan avec devis, et le plan avec devis et mesures.
Le design pattern adapté est le **Décorateur**. Le plan de base peut être considéré comme la classe minimale, et chaque mode d’affichage (mesures, devis) peut être ajouté dynamiquement en tant que décorateur.
Question 2) (1,5 point)
On souhaite appliquer successivement une série de filtres à une image, avec des filtres et un nombre variable selon les traitements.
Le design pattern recommandé est le **Composite**. Chaque filtre est un maillon de la chaîne, et l’image est associée au premier maillon. Cela permet de composer et d’exécuter les filtres de manière flexible.
Question 3) (1,5 point)
Un avatar réagit différemment selon les bonus et malus accumulés dans son parcours.
Le design pattern approprié est le **État**. L’avatar peut changer de comportement en fonction de son état, représenté par des sous-classes d’une interface commune, comme *EtatAvatar*. Chaque sous-classe implémente une réaction spécifique.
3 - Un ravalement de façade (3,5 points)
Question 1) (1 point)
Le changement de *TraceurCercle* et *TraceurRectangle* par *SuperTraceur* a-t-il des répercussions sur le code de la classe *Client* ?
Non, si le pattern Façade est respecté. L’interface de la façade ne doit pas être modifiée, et le code du client reste inchangé tant que les signatures des méthodes restent identiques.
Question 2) (1 point)
L’interface de la classe *Façade* est-elle modifiée ?
Non, elle ne doit pas l’être pour préserver le pattern Façade. L’interface doit rester stable afin d’éviter des impacts sur le code client.
Question 3) (1,5 point)
Voici les modifications apportées au diagramme de classes et au code Java :
- La façade n’est plus associée à *TraceurCercle* et *TraceurRectangle*, mais à *SuperTraceur*.
- Les méthodes *tracerCercle* et *tracerRectangle* de la façade sont mises à jour pour appeler les nouvelles méthodes de *SuperTraceur*.
Exemple de code Java :
void tracerCercle(Point centre, double rayon) {
monSuperTraceur.tracerCercle(centre, rayon);
}
void tracerRectangle(Point hautGauche, Point basDroite) {
monSuperTraceur.tracerRectangle(hautGauche, basDroite);
}
4 - L’expression numérique sous toutes ses coutures (7 points)
Question 1) (1,5 point)
Pour représenter une expression mathématique sous forme d’arbre, on peut utiliser le design pattern **Composite**. Ce modèle permet de structurer les nœuds de manière générique, en distinguant les opérateurs unaires et binaires des constantes.
Les classes principales sont :
- *NoeudExp* (classe abstraite générique pour tous les nœuds).
- *OpérateurUnaire* (classe abstraite pour les opérateurs unaires comme la négation).
- *OpérateurBinaire* (classe abstraite pour les opérateurs binaires comme l’addition et la multiplication).
- *Constante* (pour les valeurs entières).
Les associations entre opérateurs et leurs opérandes (*OperandeG*, *OperandeD*) doivent être représentées via des liens UML, sans utiliser d’attributs de type *NoeudExp*.
Question 2) (1 point)
Quelles critiques peut-on émettre sur le modèle Composite proposé ?
Ce modèle est difficile à étendre. Par exemple, ajouter une nouvelle opération (comme le calcul de statistiques) nécessite de modifier toutes les classes filles pour implémenter cette opération ou réécrire des méthodes existantes.
Question 3) (1 point)
Pour pallier ces problèmes, on utilise le design pattern **Visiteur**.
Les modifications incluent :
- Ajout de l’interface *VisiteurNoeud* avec les méthodes *visiterMultiplication*, *visiterAddition*, *visiterNegation*, et *visiterConstante*.
- Chaque nœud (*OpérateurBinaire*, *OpérateurUnaire*, *Constante*) implémente une méthode *accepter* pour déléguer l’opération au visiteur.
- Les opérations (calcul, affichage) sont encapsulées dans des classes visiteur (*VisiteurCalcul*, *VisiteurAffichage*).
Question 4) (1 point)
Diagramme de séquence pour le *VisiteurCalcul* sur un nœud de type *Multiplication* :
1. Le *VisiteurCalcul* appelle *visiterMultiplication* sur le nœud *Multiplication*.
2. Le nœud *Multiplication* appelle *visiter* sur ses deux opérandes (*OperandeG* et *OperandeD*).
3. Chaque opérande retourne sa valeur calculée via *visiter* au *VisiteurCalcul*.
4. Le *VisiteurCalcul* combine les résultats des deux opérandes pour calculer la multiplication.
Question 5) (1 point)
Exemple de programme de test pour calculer et afficher l’expression *1 + (2 * 3) + (-4)* :
NoeudExp expression = new Addition(
new Constante(1),
new Addition(
new Multiplication(
new Constante(2),
new Constante(3)
),
new Negation(new Constante(4))
)
);
VisiteurCalcul calcul = new VisiteurCalcul();
double resultat = expression.accepter(calcul);
VisiteurAffichage affichage = new VisiteurAffichage();
expression.accepter(affichage);
Question 6) (0,5 point)
Introduire une opération susceptible de lever une exception est-il possible avec le modèle Visiteur ?
Oui, mais cela nécessite de modifier toutes les méthodes de l’interface *VisiteurNoeud* pour déclarer la levée de l’exception, ainsi que toutes les méthodes *accepter* des nœuds. Cette modification n’est donc pas triviale.
Question 7) (1 point)
Pour gérer un objet *Statistique* associé à une expression numérique, avec des statistiques sélectionnables (nombre de nœuds, opérandes variables, constantes), tout en permettant l’ajout de nouvelles statistiques, on recommande le design pattern **Observateur**.
Le *NoeudExp* agit comme le sujet observé, tandis que *Statistique* (et ses sous-classes comme *StatistiqueNœuds*, *StatistiqueVariables*, *StatistiqueConstantes*) devient un observateur. Cela permet d’ajouter dynamiquement de nouvelles statistiques sans modifier le code des nœuds.
FAQ
Quelle est la différence entre une interface et une classe abstraite ?
Une interface définit un contrat de méthodes sans implémentation, tandis qu’une classe abstraite peut contenir des attributs et des méthodes implémentées communes à ses sous-classes. Le choix dépend de la présence d’attributs ou de méthodes communes.
Comment le pattern Visiteur améliore-t-il l’extensibilité du modèle Composite ?
Le pattern Visiteur encapsule les opérations dans des objets séparés, ce qui permet d’ajouter de nouvelles fonctionnalités sans modifier les classes des nœuds de l’arbre.
Pourquoi utiliser le pattern Observateur pour les statistiques ?
Le pattern Observateur permet de notifier automatiquement les observateurs (comme *Statistique*) lors de modifications de l’expression, garantissant ainsi des calculs mis à jour sans impact sur les nœuds.