# Conception orientée objet Cours de B2 sur la conception orientée objet. Cela fait suite aux quelques cours sur la RDD (responsibility driven developement) de B1. # Présentation générale Le but est d'utiliser un maximum de programmation orientée objet avancée ainsi qu'un peu de programmation fonctionnelle. Le but est de pouvoir savoir quel patron (orienté objet) utiliser pour résoudre telle ou telle situation de l'activité intégrative. ## Programmation fonctionnelle Les fonctions sont vue comme des valeurs et peuvent être passée à d'autres fonctions. La programmation fonctionnelle est plus abstraite et permet d'être plus efficace au niveau de l'écriture car on peut faire plus de choses en beaucoup moins de lignes. ## Acquis visés par le cours (UE36) - Concevoir une solution selon les principes POO - Programmer en exploitant les spécificités d'un langage de programmation - Exploiter les ressources externes (base de données, etc) - Tester en intégration (capable de fonctionner dans son ensemble) - Tester en isolation (extraire des bouts de code et leur faire passer des tests, exemples tests unitaires) ## Activité intégrative L'activité intégrative consiste en deux projets, un en Java et un en C# qui dialogue via une base de donnée relationnelle. Le travail est découpé en itérations. Les différents cours (COO, Java, C# et SD) permettent de s'entrainer dans les différents acquis mais c'est bien l'AI qui valide les acquis. ### Itérations L'AI se fait en 4 itérations
ItérationDuréeStatus
12 semainesFormative
22 semainesFormative
33 semainesCertificative
43 semainesCertificative
### Validation de l'UE - Si tous les acquis sont validés, réussite d'office (très rare) - <= 3 acquis à observer, réussite via défense orale - > 3 acquis à observer, deuxième session (même projet + nouveautés) ### Difficulté La difficulté n'est pas dans la difficulté du projet ou des algorithmes mais dans l'organisation et la charge de travail. Il faut donc faire très attention à bien s'organiser et ne pas prendre de retard. # Introduction à la conception et patron stratégie ## Ressources - *[Head First Design Pattern](https://annas-archive.org/md5/2cf0e1f488d4c588c93004de7d88d72d)*, la bible de la COO, assez simple et ludique à lire, très recommandé et avec beaucoup d’illustrations. - *[Plongée au coeur des patrons de conception](https://annas-archive.org/md5/6522944106a4e5bf95eccdfa9760c101)*, créé par le site de Refactoring Guru mais un peu moins accessible que l’autre. En revanche celui ci est disponible en français. - Le site *[Refactoring Guru](https://refactoring.guru/fr)* qui est vraiment bien, avec de bonnes illustrations et desdescriptions simples de chaque pattern. ## Conception ### Exemple Imaginons que dans un cas nous avons un code fonctionnant en hierarchie de classe, comment faire pour transformer une classe en une autre ? Ce n'est pas possible, c'est donc une erreur de conception. ### Définition La conception permet de trouver des solutions permetant de structurer le code pour favoriser sa **maintenabilité** (modification du code) et son **extensibilité** (ajouter du code) car une application, quoi qu'il arrive doit absolument grandir et changer, sinon elle va mourir. On veut donc *structurer* le code pour faire émerger des qualités désirables en fonction des usages (performance, stabilité, fiabilité, etc) Structurer un code c'est faire les liens entre les différents éléments/modules d'une application. Le niveau au dessus c'est l'architecture, qui établi les liens entre différentes applications d'un système. ### Principes de la conception #### Etape 1 - Séparer les occupations Tout d'abord on doit se demander : - Qu'est ce qui varie ? - Qu'est ce qui ne changer pas ? On va donc séparer ce qui change de ce qui ne change pas (très important!) [![2023-09-19_19-33-59_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-19-19-33-59-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-19-19-33-59-screenshot.png) Par exemple ici, on veut que les personnages puissent changer d'ordre (c'est donc ce qui varie ici), en revanche BaseCharacter change pas. On va donc séparer les ordres des personnages. #### Etape 2 - Programmer avec des interfaces et éviter les types concrets On va donc faire un lien entre ce qui change et ce qui ne change pas. Par exemple ici on peut utiliser une interface pour lier les ordres avec les personnages. [![2023-09-19_19-36-34_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-19-19-36-34-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-19-19-36-34-screenshot.png) Ici on peut créer une interface "Ordre" pour lier les différents ordres. Ainsi si on veut en créer un nouveau, il suffit de créer une nouvelle classe implémentant l'interface. #### Etape 3 - Favoriser la composition à l'héritage Enfin il faut pouvoir mémoriser le lien, pour cela dans notre exemple on peut simplement faire un attribut de type Order (notre interface). Il vaut mieux utiliser l'attribut (qui est plus flexible) plus tot que l'héritage qui est beaucoup plus rigide. Maintenant il faut pouvoir lier les ordres avec les peronnages. Pour cela on peut créer un attribut "ordre" demandant un type de notre interface "Ordre" dans la classe BaseCharacter. ```java public class BaseCharacter { private final String uniqueName; private final int[] points; private OrderBehavior order = new NoOrderBehavior(); // ← composition de ordre dans basecharacter public static final int HIT_POINTS = 0; public static final int DAMAGE_POINTS = 1; public BaseCharacter(String uniqueName, int hitPoints, int damagePoints) { this.uniqueName = StringExtensions.requireNotBlank(uniqueName, "uniqueName").strip(); this.points = new int[]{ NumberUtils.requireGrEq(hitPoints, 0, "hitPoints"), NumberUtils.requireGrEq(damagePoints, 0, "damagePoints") }; } /*Reste du code*/ public String attack(BaseCharacter target) { return order.attack(this, target); } public void setOrder(OrderBehavior order) { this.order = order; } } ``` #### Etape 4 - Injecter les dépendences Il est important de ne pas avoir de valeurs null pour un attribut, cependant il faut aussi éviter d'avoir un type concret. Alors pour définir la classe sans utiliser de classe concretes dedans, on peut simplement la demander (et l'obliger) dans le constructeur (soit, faire une **injection de dépendence**). Ainsi ce code (qui ne respecte pas l'étape 2, car elle utilise un type concret NoOrderBehavior) : ```java public class BaseCharacter { private final String uniqueName; private OrderBehavior order = new NoOrderBehavior(); // berk } ``` Deviendra à ce code (avec l'injection de dépendence) ```java public class BaseCharacter { private final String uniqueName; private OrderBehavior order; public BaseCharacter(String uniqueName, OrderBehavior order) { this.uniqueName = uniqueName; this.order = Objects.requireNonNull(order); } } ``` ### Les patrons La solution que l'on a trouvé dans l'exemple précédent, nous avons utilisé le patron "stratégie" (qui est l'une des plus basique). Les patrons de conceptions sont des solutions éprouvées à des problèmes réccurents et qui peuvent être facilement adapté à des problèmes spécifiques. [![2023-09-19_19-56-34_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-19-19-56-34-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-19-19-56-34-screenshot.png) #### Catégories Il existe plusieurs catégories de patrons de conceptions. - Les patrons **comportementaux** qui proposent des solutions aux problèmes de collaboration et d'affectation des responsabilités entre objets (c'est le cas du patron stratégie) - Les patrons **créationnels** (créer des objets de façon flexible) - Les patrons **structurels** (assemblent des objets en structures flexibles) En vérité on s'en fout des catégories, ce qui compte c'est *l'intention* de chaque pattern (patron). #### Description d'un patron Chaque patron compte 4 sections: - L'**intention**, qui décrit brièvement le problème et la solution - La **motivation**, qui explique en détail la problématique et la solution offerte par le patron - La **structure**, montre les différentes parties du patron et leurs relations - L'**exemple de code**, écrit dans un des langages de programmation les plus populaire Pour voir cette description en action, on peut simplement aller voir le site [Refactoring Guru](https://refactoring.guru/fr/design-patterns/strategy). ## Avantages - **Réutilisabilité** : Les design patterns offrent des solutions génériques à des problèmes communs - **Modularité** : ils favorisent la séparatoin des préoccupations, ce qui facilite la maintenance et l'évolutivité de l'application. - **Compréhensibilité**, ils sont bien documents et largement reconnus, ce qui rends le code plus compréhensible pour les développeur·euse qui connaissent ces modèles. - **Interopérabilité**, ils permettent de standardiser les solutions à des problèmes courants. Cela favorise l'interopérabilité entre les différentes parties d'une application ou entre différentes applications. ## Eviter les généralisations spéculatives Il faut vraiment attendre qu'un design pattern soit vraiment nécessaire pour le mettre en place pour ne pas intégrer une complexité inutile au code. # Les observables ## Problème Lorsque l'on a des évènements (par exemple nouvel articles) et que l'on a plusieurs terminaux pour recevoir cet evènement (écrans, notifications, emails, etc). Si on fait simplement tout dans une seule classe, cela enfreint le principe d **ouvert-fermé** (c'est à dire qu'un module doit être ouvert au changements sans avoir à le modifier). Car ici pour ajouter ou supprimer des terminaux (observateurs), on est obligé de modifier tout le module. ## Solution [![2023-09-19_20-12-09_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-19-20-12-09-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-19-20-12-09-screenshot.png) La classe qui publie les évènements va stoquer une liste d'observateurs et va implémenter des méthodes pour que les observateurs puissent s'abonner ou se désabonner. Les observateurs vont implémenter l'interface "Obervateur" qui va simplement contenir une méthode pour recevoir l'évènement. A present pour ajouter ou supprimer des terminaux il suffit d'abonner ou désabonner un observateur à la classe publisher. ## Critique Ce système a beaucoup d'avantages car il respecte le principe ouvert-fermé, il sépare les préoccupations et permet l'abonnement pendant l'exécution. Cependant, il rends aussi le code plus complexe et plus lent (car si un des observateurs est lent, il ralenti toute l'exécution et c'est généralement assez couteux de le paralléliser). Il ne faut pas l'utiliser tout de suite ou tout le temps. Il faut seulement le faire lorsque c'est vraiment nécessaire (par exemple ne pas le faire si il n'y qu'un observateur). # Les fabriques et ponts Le nom fabrique est un peu utilisé à toutes les sauces, il existe [un article de RefactoringGuru](https://refactoring.guru/fr/design-patterns/factory-comparison)qui liste les différences entre les différentes appellations du mot. ## Fabriques ### Exemple du problème On veut créer une classe permettant de créer et entrainer les StormTroopers. On a donc fait ceci : ```java public class StormtrooperTrainingFacility { // Cette méthode va créer et entrainer les stormtroopers // MilitarySection est une enum permettant de distinguer les différents types public Stormtrooper createAndPrepare(MilitarySection section) { // Un Stormtrooper du bon type est généré en fonction de la MilitarySection Stormtrooper product = switch(section) { case ASSAULT -> new AssaultStormTrooper(); case GRENADIER -> new GrenadierStormTrooper(); case PILOT-> new PilotStormTrooper(); default-> throw new IllegalArgumentException("section"); }; // Ensuite les stormtroopers vont être entrainé product.equip(); product.train(); product.passExam(); // Puis retourné return product; } } ``` Dans le code précédent, le code a plusieurs problèmes : - Il fait plusieurs choses (le training et la création d'objets), ce qui fait que si quelque chose change, il va falloir créer de nouvelles conditions. - Il connait trop de classes concrètes (problème d'injection des dépendences) - Si la création d'un objet change, le code de préparation devra changer aussi - De plus la structure des StormTroopers est basé sur l'héritage ce qui rends tout beaucoup plus compliqué ### Solution : La fabrique simple #### Théorie [![2023-09-26_16-23-17_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-26-16-23-17-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-26-16-23-17-screenshot.png)*Il vaut tout de même utiliser un enum pour définir le nom du de la chose à produire* On extrait la partie de la création dans une classe Factory à part avec une méthode statique "create" La "fabrique simple" permet de séparer la responsabilité d'utiliser un objet, de celle de la créer. La méthode de fabrique simple peut aussi être appellée "méthode de fabrique statique polymorphe". #### Exemple ```java public class StormtrooperFactory { // Cette méthode va créer les stormtroopers // MilitarySection est une enum permettant de distinguer les différents types public static Stormtrooper create(MilitarySection section) { // Un Stormtrooper du bon type est généré en fonction de la MilitarySection return switch(section) { case ASSAULT -> new AssaultStormTrooper(); case GRENADIER -> new GrenadierStormTrooper(); case PILOT-> new PilotStormTrooper(); default-> throw new IllegalArgumentException("section"); }; } } ``` ### Anti-pattern : La méthode de fabrique #### Théorie [![2023-09-26_16-52-46_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-26-16-52-46-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-26-16-52-46-screenshot.png) Quand on a vraiment beaucoup de produits et que ça devient un bordel de tout avoir au même endroit, on peut utiliser de patron de conception pour séparer les choses dans plusieurs classes. Dans ce patron de conception, on a donc plusieurs sous-fabriques qui héritent d'une classe fabrique. La classe va donc créer des Produits et chaque produit concret va implémenter la classe Produit. Ce patron est également caca, il faut donc éviter de l'utiliser car il y abeaucoup trop de relations d'héritages et cela va créer beaucoup trop de classes. #### Exemple Pour donner un exemple de son utilisation et du bordel qu'il cause, imaginons que l'on aie des StormTroopers Wookie en plus des humains. Selon ce pattern on devrait créer des sous fabriques héritant d'une fabrique principale abstraite. Ce qui donnerait ceci : [![2023-09-26_17-30-03_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-26-17-30-03-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-26-17-30-03-screenshot.png) - Le **Creator** ici est la classe abstraite "StormtrooperTrainingFacility" - Le **Product** ici est l'interface Stormtrooper - Les **Concrete Products** sont les classes AssaultStormtrooper, GrenadierStormtrooper et PilotStormtrooper - Les classes WookieAssaultStormTrooper, WookieGrenadierStormTrooper et WookiePilotStormtrooper (et leurs équivalents humains) héritent de Concrete Products et sont donc aussi des Concrete Products - Les **Concrete creators** sont les classes HumanStormtrooperTrainingFacility et WookieStormtrooperTrainingFacility ### Anti-pattern : Fabrique abstraite #### Théorie [![2023-09-26_17-12-28_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-26-17-12-28-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-26-17-12-28-screenshot.png) La fabrique abstraite est utilisée lorsqu'on a besoin de créer des familles d'objets sans préciser leurs classes concrete. Dans cette fabrique… tout est trop compliqué avec beaucoup trop de relations d'héritages. Cette fabrique a tous les désavantages du précédent, en pire. Ne surtout pas l'utiliser. Encore une fois, ce patron favorise l'héritage à la composition. #### Exemple Vous ne voulez pas savoir… ## Le pont ### Théorie Bien que les méthodes de fabrique (en français simplement appellée "Fabrique") et la fabrique abstraite sont listé comme des patterns dans le site Refactoging Guru, ils sont en vérité plus des anti-patterns (comme toute chose qui utilise l'héritage d'ailleurs). Le problème de l'exemple avec la méthode de fabrique, est que l'espèce et la spécialisation du Stormtrooper étaient mélangées (par exemple dans une seule classe "WookieAssaultStormTrooper", "HumanAssaultStormTrooper" ou encore "WookieGrenadierStormTrooper") et ce alors que leur spécialisation et leur espèce sont deux choses complètements distinctes. C'est là que le patron de conception du Pont entre en jeu, il sert à diviser ces grosses classes, permettant donc les espèces et les spécialisations d'évoluer indépendamment. [![2023-09-26_17-26-14_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-26-17-26-14-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-26-17-26-14-screenshot.png) *Ce diagramme donne met Refined Abstraction comme héritant de la classe Abstraction mais c'est une meilleure idée d'éviter l'héritage en transformant l'Abstraction en interface à implémenter* ### Exemple Pour reprendre notre exemple de départ, à la place d'avoir un *WookieAssaultStormTrooper* on va avoir un *AssaultStormtrooper* qui a comme attribut "espèce" "Wookie". On a donc séparé les espèces et les sépcialisations en deux ensembles de classes distinctes (on a donc séparé les préoccupations et favorisé la composition à l'héritage). [![2023-09-26_17-21-15_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-09/scaled-1680-/2023-09-26-17-21-15-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-09/2023-09-26-17-21-15-screenshot.png) - L'**abstraction** ici est l'interface Stormtrooper - L'**abstraction fine** ici sont les classes AssaultStormtrooper, GrenadierStormtrooper et PilotStormtrooper - L'interface **implémentation** ici est l'interface AntropomorphKind (ou Espèce) - Les classes **implémentations concrètes** sont ici Human et Wookie ## Résumé En résumé les deux patrons de conceptions à retenir ici sont ceux de la fabrique simple et du Pont. - La fabrique simple permet de simplement créer des objets de différents types (de cette façon se concentrer uniquement sur la création des objets) - Le pont sert à simplifier l'architecture du code de façon a séparer les préoccupations (par exemple préoccupation "Spécialisation" et préoccupation "Espèce") Si on utilise correctement le pont on ne devrait jamais avoir besoin d'autre chose que de la fabrique simple pour créer les objets. # Façade Le patron de la façade permet d'avoir un accès simplifié à un ensemble complexe de classes. [![2023-10-03_22-23-13_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-10/scaled-1680-/2023-10-03-22-23-13-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-10/2023-10-03-22-23-13-screenshot.png) Voici à quoi ressemble le patron de la façade : [![2023-10-03_22-24-05_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-10/scaled-1680-/2023-10-03-22-24-05-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-10/2023-10-03-22-24-05-screenshot.png) Le fonctoinnement de la facade est que l'on crée une classe facade qui collabore avec les éléments du système pour fournir une interface simple au client. Cependant si la facade devient elle même trop imposante, on peut utiliser faire communiquer une façade avec une autre façade. Les façades permettent ainsi d'éviter les God objects et les God functions (les objets ou fonctions qui font trop de choses et connaissent trop de classes). ## Cohésion et couplage [![2023-10-03_22-42-34_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-10/scaled-1680-/2023-10-03-22-42-34-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-10/2023-10-03-22-42-34-screenshot.png) Le patron de la façade a également l'avantage d'augmenter la cohésion, c'est à dire d'augmenter le degré d'interconnexion des membres du système du quel la facade fait partie. Une cohésion forte indique que le type représente un concept clair et réutilisable. Cependant si le couplage est trop élevé, on arrive dans un God Object (précisément ce que l'on souhaite éviter), c'est pour cela qu'il vaut mieux créer plusieurs façades si une façade devient un peu trop omnisciente. # Adaptateur Le patron de l'adaptateur sert à faire collaborer des objets qui ont une interface incompatible. [![2023-10-03_22-31-41_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-10/scaled-1680-/2023-10-03-22-31-41-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-10/2023-10-03-22-31-41-screenshot.png) Le fonctionnement est que l'on crée des classes adaptateurs entre une interface et un service. Voici un exemple : [![2023-10-03_22-33-04_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-10/scaled-1680-/2023-10-03-22-33-04-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-10/2023-10-03-22-33-04-screenshot.png) Dans cet exemple, l'Examinator est notre client, il utilise l'interface "ExamResultRepository". Les classes CsvExamResultRepository et SqlExamResultRepository sont les adaptateurs qui implémente l'interface. Ces adaptateurs vont ensuite utiliser les services appropriés, dans le cas présent, les fonctions de `java.nio` et `java.sql`. Cela permet ainsi de séparer les préoccupations, de programmer avec des interfaces et d'éviter l'héritage. Cependant ce patron a le désavantage que si il est utilisé de manière appropriée il peut complexifier le code, il faut donc d'abord se demander si il n'est pas plus simple de changer directement les services pour les faire utiliser une interface commune, plus-tôt que de créer des adaptateurs (voir [Conception et découverte du patron Strategie](https://books.snowcode.ovh/books/conception-orientee-objet/page/introduction-a-la-conception-et-patron-strategie). Dans le cas de l'exemple, les adaptateurs sont une bonne idée car on ne peut pas modifier les services qui font simplement partie du JDK. # MVP (Modèle Vue Présentateur) ## Patron architectural Un patron architectural est une solution générale et réutilisable à un problème architectural, comme les patrons de conceptions mais ont une portée plus large. Dans le cas de celui que l'on va voir ici, on remarquera qu'il est lui même composé des patrons [Façade](https://books.snowcode.ovh/books/conception-orientee-objet/page/facade) et [Adaptateurs](https://books.snowcode.ovh/books/conception-orientee-objet/page/adaptateur) vus précédemment. ## MVP Voici ce que l'on obtient quand on combine les deux derniers patterns (façade et adaptateurs) : [![2023-10-03_22-53-13_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-10/scaled-1680-/2023-10-03-22-53-13-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-10/2023-10-03-22-53-13-screenshot.png) Ici les classes \*ExaminatorView et \*ResultRepository sont des adaptateurs, tandis que les classes de java sont les services. Mais on peut aussi y voir la façade car la classe Examinator agit comme une façade pour le reste du système. En vérité, cette structure correspond au patron architectural "MVP" (Modèle Vue Présentateur) [![2023-10-03_22-56-00_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-10/scaled-1680-/2023-10-03-22-56-00-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-10/2023-10-03-22-56-00-screenshot.png) Dans l'exemple précédent : - Examinator est le Présentateur (il n'intéragit qu'avec la vue et le modèle). - ExamResultRepository est le Modèle qui s'occupe de tout ce qui est de la gestion des données (oui dans le MVP le modèle est simplement une classe et pas une interface, mais cela dépends simplement des besoins de l'application) - ExaminatorView est l'interface des vues - ConsoleExaminatorView et SwingExaminatorView sont les classes concrètes de la vue. [![2023-10-03_22-59-21_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-10/scaled-1680-/2023-10-03-22-59-21-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-10/2023-10-03-22-59-21-screenshot.png) ## Caractéristiques du MVP - Une vue ne conait que son présentateur - Le présentateur supervise les vue via une interface - Le présentateur met à jour le modèle du domaine puis la vue - Le patron de l'observateur peut être envisagé pour affaiblir le couplage # Les chaines de responsabilités La chaine de responsabilité est un moyen de faire beaucoup de traitement sur un même objet. Cela peut être un très bon moyen de gérer une cascade de conditions `if` dans un code. Ainsi chaque bloc de if est séparé et sont liés entre eux. Cela permet aussi d'isoler ces différentes vérifications dans des fichiers séparés. [![2023-10-03_19-50-33_screenshot.png](https://books.snowcode.ovh/uploads/images/gallery/2023-10/scaled-1680-/2023-10-03-19-50-33-screenshot.png)](https://books.snowcode.ovh/uploads/images/gallery/2023-10/2023-10-03-19-50-33-screenshot.png) A noter que ce schéma utilise l'héritage mais qu'il faut toujours préférer la composition à l'héritage, il vaut donc mieux simplement avoir des ConcreteHandlers qui implémentent tous Handler. Aussi pour simplifier l'écriture on peut simplement mettre le `setNext`dans le constructeur du Handler (ce qui permet de rendre la chaine plus propre par après) ## Exemple ### Code avant Voici l'horrible code à réorganiser : ```java package com.gildedrose; class GildedRose { Item[] items; public GildedRose(Item[] items) { this.items = items; } public void updateQuality() { for (int i = 0; i < items.length; i++) { if (!items[i].name.equals("Aged Brie") && !items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { if (items[i].quality > 0) { if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { items[i].quality = items[i].quality - 1; } } } else { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; if (items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { if (items[i].sellIn < 11) { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; } } if (items[i].sellIn < 6) { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; } } } } } if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { items[i].sellIn = items[i].sellIn - 1; } if (items[i].sellIn < 0) { if (!items[i].name.equals("Aged Brie")) { if (!items[i].name.equals("Backstage passes to a TAFKAL80ETC concert")) { if (items[i].quality > 0) { if (!items[i].name.equals("Sulfuras, Hand of Ragnaros")) { items[i].quality = items[i].quality - 1; } } } else { items[i].quality = items[i].quality - items[i].quality; } } else { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1; } } } } } } ``` ### Code après Et voici ce que l'on obient en utilisant le patron de la chaine de reponsabilités. - Classe principale GildedRose : ```java package com.gildedrose; import com.gildedrose.handlers.*; class GildedRose { Item[] items; public GildedRose(Item[] items) { this.items = items; } public void updateQuality() { // On enchaine les handlers // Ainsi Sulfuras >> AgedBrie >> BackStage >> Default Handler handlers = new Sulfuras(new AgedBrie(new Backstage(new Default()))); for (int i = 0; i < items.length; i++) handlers.update(items[i]); } } ``` - L'interface Handler ```java package com.gildedrose.handlers; import com.gildedrose.Item; public interface Handler { void update(Item item); } ``` - Les différentes classes implémentant Handler (notez qu'ici toutes les classes sont mise au même endroit, mais dans le projet elles sont dans des fichiers séparés) ```java package com.gildedrose.handlers; import com.gildedrose.Item; public class AgedBrie implements Handler { private Handler next = null; public AgedBrie(Handler next) { this.next = next; } @Override public void update(Item item) { if (item.name.equals("Aged Brie")) { item.sellIn--; if (item.quality < 50) item.quality++; if (item.sellIn < 0 && item.quality < 50) item.quality++; } else if (next != null) next.update(item); } } public class Backstage implements Handler { private Handler next = null; public Backstage(Handler next) { this.next = next; } @Override public void update(Item item) { if (item.name.equals("Backstage passes to a TAFKAL80ETC concert")) { if (item.quality < 50) item.quality++; if (item.sellIn < 11 && item.quality < 50) item.quality++; if (item.sellIn < 6 && item.quality < 50) item.quality++; // I have no idea what this shit does if (item.sellIn < 0) item.quality = item.quality - item.quality; } else if (next != null) { next.update(item); } } } public class Default implements Handler { @Override public void update(Item item) { if (item.quality > 0) { item.quality--; item.sellIn--; if (item.sellIn < 0) item.quality--; } } } public class Sulfuras implements Handler { private Handler next = null; public Sulfuras(Handler next) { this.next = next; } @Override public void update(Item item) { if (item.name.equals("Sulfuras, Hand of Ragnaros")) return; if (next != null) next.update(item); } } ```