Cours BUT Info

Ch. 5 — Introduction aux patrons de conception

Pourquoi le design se dégrade et quels principes le protègent — puis les patrons de conception : familles GoF et trois patrons en profondeur, Singleton (5 implémentations), Facade et Factory.

La lente dégradation du design

La vie d'un fichier source est connue : pur et « beau » à la création, première hérésie au premier changement, horreurs ensuite — effet spaghetti garanti. Les dégradations naissent des modifications des spécifications : toujours « à faire vite », par d'autres que les concepteurs originaux. « Ça marche », mais la conception est corrompue — et plus la dégradation survient tard, pire c'est. Or les modifications sont inévitables : la conception doit les permettre et les amortir. Les dégradations sont dues aux dépendances et à leur architecture.

Les principes qui protègent

  • OCP (Open/Closed) : ouvert à l'extension, fermé à la modification — étendre l'application sans toucher à ce qui existe et fonctionne ;
  • LSP (Liskov) : les sous-classes doivent pouvoir remplacer leur classe de base — utiliser « inconsciemment » des objets dérivés doit rester possible. Autrement dit : un up-cast sémantiquement correct ;
  • DIP (Dependency Inversion, alias Inversion of Control) : le crédo — « dépendez des abstractions, ne dépendez jamais des concrétisations ». Haut niveau et bas niveau ne se dépendent pas : tous dépendent d'abstractions ;
  • ISP (Interface Segregation) : plusieurs interfaces client-spécifiques valent mieux qu'une interface générale — un client ne voit pas ce qui ne le concerne pas, et l'évolution d'un service n'impacte pas les clients non concernés.

Constat : des modules métiers bâtis directement sur le bas niveau changent dès que celui-ci change, et sont irréutilisables hors du contexte technique. Solution : le bas niveau se conforme aux interfaces définies par le haut niveau. Deux vices à chasser : la cohésion trop faible — GameBoard gère règles (isWinner, isTie) et affichage ; on extrait BoardDisplay — et le couplage inutile — initArray(tab, nStudents) reçoit un paramètre redondant avec iGradeArray.length ; initArray(tab) fiabilise.

Qu'est-ce qu'un patron de conception ?

Un patron de conception (design pattern) est une solution générique — un schéma qui résout un problème spécifique — due à l'expérience des programmeurs. Il décrit une structure de classes utilisant des interfaces, donc s'applique à l'orienté objet ; on l'utilise pour diminuer le temps de développement et augmenter la qualité du résultat. Plan GoF : nom et classification, intent, motivation, structure (UML), applicability, participants, collaborations, consequences, implementation, sample code, known uses, related patterns.

Les patrons ne sont que des guidelines : plusieurs implémentations restent possibles, à la responsabilité du développeur. Le choix dépend du contexte — accès concurrents ou non, fréquence de requête de l'instance, création sous réserve d'utilisation (connexion à une base de données). Chaque interprétation a ses avantages et ses inconvénients.

Les trois familles

  • Création — abstraction de l'instanciation, pour rendre le système indépendant du mode de création de ses objets : Builder, Prototype, Factory, Abstract Factory, Singleton ;
  • Structure — composition de classes et d'objets en structures plus complexes : Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy ;
  • Comportement — algorithmes et répartition des responsabilités, communication entre objets : Chain of responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template, Visitor.

Singleton : une seule instance, cinq recettes

Objectif : garantir une seule et unique instance d'une classe dans l'application et fournir le moyen de l'obtenir — typique des pools, caches, bases de données, loggers. La recette UML : attribut statique instance, constructeur private, méthode statique getInstance().

Lazy initialization — création à la première demande :

public class MySingletonLazyInit {
    private static MySingletonLazyInit instance;
    private MySingletonLazyInit() {}
    public static MySingletonLazyInit getInstance() {
        if (instance == null) instance = new MySingletonLazyInit();
        return instance;
    }
}

Impeccable en mono-thread — deux appels de getInstance() renvoient bien la même référence — mais aucune gestion des accès concurrents. Eager initialization — private static final ... instance = new ... : la plus facile, mais le singleton est créé même si la ressource ne sert jamais (critique pour bases de données ou système de fichiers), et impossible de gérer les exceptions. Bloc statique — un static {} enveloppe la création d'un try/catch : exceptions gérables, instance créée d'office quand même. Thread-safe — public static synchronized getInstance() : accès concurrents gérés, au prix du coût des méthodes synchronisées (variantes : double-checked locking, Bill Pugh). Enum — la plus courte : public enum MySingletonEnum { INSTANCE; } ; peu malléable, créée même sans usage.

Facade : simplifier un sous-système

Quand des clients utilisent directement plusieurs classes d'un sous-système (ClasseA, ClasseB, ClasseC), le couplage explose. La Facade propose une interface de plus haut niveau qui réalise les appels nécessaires pour offrir des fonctionnalités cohérentes : le code client se réduit, la maintenance se facilite, une couche d'abstraction s'interpose. Règle d'or : les classes du sous-système ne connaissent pas la façade. Usages : faciliter l'utilisation (même partielle) d'un sous-système complexe, masquer son existence, lui ajouter des fonctionnalités sans le modifier, découpler client et sous-système. Une façade sans état est pratique — une seule instance côté client — et si elle encapsule le sous-système, le remplacer ne demande de modifier qu'elle.

Factory : déléguer la création

Objectif : créer un objet dont le type dépend du contexte — déterminé dynamiquement à l'exécution parmi un ensemble de sous-classes — et centraliser la création. L'objet retourné est toujours du type de la classe mère ; grâce au polymorphisme, les traitements exécutés sont ceux de l'instance créée.

Version complète : ProduitFactory propose getProduitA(), qui retourne l'instance créée par la méthode protégée createProduitA(), redéfinie par les fabriques concrètes ProduitFactory1 et ProduitFactory2 ; ProduitA est la classe abstraite mère, ProduitA1/ProduitA2 les produits concrets. Version légère : une seule classe dotée d'une méthode paramétrée — getProduitA(int) reçoit une constante précisant le type à créer — la création dépendant de données du contexte : paramètres, fichier de configuration, arguments de l'application.

A retenir

  • le design se dégrade par les dépendances ; OCP, LSP, DIP, ISP le protègent ;
  • un patron = une solution générique éprouvée, décrite au plan GoF, appliquée selon le contexte ;
  • trois familles : création, structure, comportement ;
  • Singleton : constructeur privé + getInstance(), cinq implémentations au choix ;
  • Facade masque un sous-système ; Factory délègue la création au polymorphisme.


coursQU_05.pdf

R2.03 · Cours 5 — Introduction aux patrons de conception · 42 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

Dernière mise à jour le

Sur cette page