Introduction aux modèles de conception, Partie 1: Historique et classification des modèles de conception

De nombreuses stratégies ont été élaborées pour simplifier et réduire les coûts de conception des logiciels, notamment dans le domaine de la maintenance. Apprendre à identifier et à travailler avec des composants logiciels réutilisables (parfois appelés circuits intégrés logiciels ) est une stratégie. L'utilisation de modèles de conception en est une autre.

Cet article lance une série en trois parties sur les modèles de conception. Dans cette partie, je vais présenter le cadre conceptuel des modèles de conception et parcourir une démonstration de l'évaluation d'un modèle de conception pour un cas d'utilisation particulier. Je discuterai également de l'histoire des modèles de conception et des anti-modèles. Enfin, je vais classer et résumer les modèles de conception de logiciels les plus utilisés qui ont été découverts et documentés au cours des deux dernières décennies.

Qu'est-ce qu'un modèle de conception?

Concevoir un logiciel orienté objet réutilisable qui modélise un système existant est un véritable défi. Un développeur de logiciel doit factoriser les entités du système dans des classes dont les interfaces publiques ne sont pas trop complexes, établir des relations entre les classes, exposer les hiérarchies d'héritage, etc. Étant donné que la plupart des logiciels restent utilisés longtemps après leur écriture, les développeurs de logiciels doivent également répondre aux exigences actuelles des applications tout en maintenant leur code et leur infrastructure suffisamment flexibles pour répondre aux besoins futurs.

Les développeurs orientés objet expérimentés ont découvert que les modèles de conception de logiciels facilitent le codage de systèmes logiciels stables et robustes. Réutiliser ces modèles de conception plutôt que de développer constamment de nouvelles solutions à partir de zéro est efficace et réduit une partie du risque d'erreur. Chaque modèle de conception identifie un problème de conception récurrent dans un contexte d'application spécifique, puis propose une solution généralisée et réutilisable qui s'applique à différents scénarios d'application.

"Un modèle de conception décrit les classes et les objets en interaction utilisés pour résoudre un problème de conception général dans un contexte spécifique."

Certains développeurs définissent un modèle de conception comme une entité codée par classe (telle qu'une liste chaînée ou un vecteur de bits), tandis que d'autres disent qu'un modèle de conception est dans l'ensemble de l'application ou du sous-système. Mon point de vue est qu'un modèle de conception décrit les classes et les objets en interaction utilisés pour résoudre un problème de conception général dans un contexte spécifique. Plus formellement, un modèle de conception est spécifié comme une description qui se compose de quatre éléments de base:

  1. Un nom qui décrit le modèle de conception et nous donne un vocabulaire pour en discuter
  2. Un problème qui identifie le problème de conception à résoudre ainsi que le contexte dans lequel le problème se produit
  3. Une solution au problème, qui (dans un contexte de modèle de conception de logiciel) devrait identifier les classes et les objets qui contribuent à la conception ainsi que leurs relations et d'autres facteurs
  4. Une explication des conséquences de l'utilisation du modèle de conception

Afin d'identifier le modèle de conception approprié à utiliser, vous devez d'abord identifier clairement le problème que vous essayez de résoudre; c'est là que l' élément problématique de la description du modèle de conception est utile. Le choix d'un modèle de conception plutôt qu'un autre implique généralement des compromis qui peuvent avoir un impact sur la flexibilité d'une application ou d'un système et sur la maintenance future. C'est pourquoi il est important de comprendre les conséquences de l'utilisation d'un modèle de conception donné avant de commencer à l'implémenter.

Évaluer un modèle de conception

Envisagez la tâche de concevoir une interface utilisateur complexe à l'aide de boutons, de champs de texte et d'autres composants non-conteneur. Le modèle de conception composite considère les conteneurs comme des composants, ce qui nous permet d'imbriquer les conteneurs et leurs composants (conteneurs et non-conteneurs) dans d'autres conteneurs, et ce de manière récursive. Si nous choisissons de ne pas utiliser le modèle Composite, nous devrons créer de nombreux composants non-conteneurs spécialisés (un seul composant combinant un champ de texte de mot de passe et un bouton de connexion, par exemple), ce qui est plus difficile à réaliser.

Après avoir bien réfléchi, nous comprenons le problème que nous essayons de résoudre et la solution offerte par le modèle composite. Mais quelles sont les conséquences de l'utilisation de ce modèle?

L'utilisation de Composite signifie que vos hiérarchies de classes mélangeront des composants conteneur et non conteneur. Les clients plus simples traiteront uniformément les composants des conteneurs et des autres composants. Et il sera plus facile d'introduire de nouveaux types de composants dans l'interface utilisateur. Le composite peut également conduire à des conceptions trop généralisées , ce qui rend plus difficile de restreindre les types de composants qui peuvent être ajoutés à un conteneur. Puisque vous ne pourrez pas compter sur le compilateur pour appliquer les contraintes de type, vous devrez utiliser des vérifications de type à l'exécution.

Quel est le problème avec les vérifications de type d'exécution?

Les vérifications de type à l'exécution impliquent des instructions if et l' instanceofopérateur, ce qui conduit à un code fragile. Si vous oubliez de mettre à jour une vérification de type d'exécution à mesure que les exigences de votre application évoluent, vous pourriez par la suite introduire des bogues.

Il est également possible de choisir un modèle de conception approprié et de ne pas l'utiliser correctement. Le schéma de verrouillage Double-vérifié est un exemple classique. Le verrouillage revérifié réduit la surcharge d'acquisition du verrou en testant d'abord un critère de verrouillage sans acquérir réellement le verrou, puis en acquérant uniquement le verrou si la vérification indique que le verrouillage est nécessaire. Bien que cela ait l'air bien sur le papier, le verrouillage double-vérifié dans JDK 1.4 présentait des complexités cachées. Lorsque JDK 5 a étendu la sémantique du volatilemot - clé, les développeurs ont finalement pu profiter des avantages du modèle de verrouillage Double-vérifié.

En savoir plus sur le verrouillage à double contrôle

Voir "Verrouillage vérifié: intelligent, mais cassé" et "Le verrouillage vérifié deux fois peut-il être corrigé?" (Brian Goetz, JavaWorld) pour en savoir plus sur les raisons pour lesquelles ce modèle ne fonctionnait pas dans JDK 1.4 et versions antérieures. Pour plus d'informations sur la spécification de DCL dans JDK 5 et versions ultérieures, voir «La déclaration« Double-Checked Locking is Broken »» (Département d'informatique de l'Université du Maryland, David Bacon, et al.).

Anti-motifs

Lorsqu'un modèle de conception est couramment utilisé mais est inefficace et / ou contre-productif, le modèle de conception est appelé anti-modèle . On pourrait affirmer que le verrouillage à double vérification utilisé dans JDK 1.4 et les versions antérieures était un anti-pattern. Je dirais que c'était simplement une mauvaise idée dans ce contexte. Pour qu'une mauvaise idée évolue vers un anti-modèle, les conditions suivantes doivent être remplies (voir Ressources).

  • Un modèle répété d'action, de processus ou de structure qui semble initialement bénéfique, mais qui produit finalement plus de mauvaises conséquences que de résultats bénéfiques.
  • Il existe une solution alternative qui est clairement documentée, éprouvée dans la pratique et reproductible.

Alors que le verrouillage double-vérifié dans JDK 1.4 répondait à la première exigence d'un anti-motif, il ne répondait pas à la seconde: bien que vous puissiez utiliser synchronizedpour résoudre le problème de l'initialisation paresseuse dans un environnement multithread, cela aurait vaincu la raison de en utilisant le verrouillage vérifié en premier lieu

Anti-motifs de blocage

Reconnaître les anti-modèles est une condition préalable pour les éviter. Lisez la série en trois parties d'Obi Ezechukwu pour une introduction à trois anti-modèles célèbres pour provoquer une impasse:

  • Pas d'arbitrage
  • Agrégation des travailleurs
  • Verrouillage incrémental

Historique des modèles de conception

Les modèles de conception remontent à la fin des années 1970 avec la publication de A Pattern Language: Towns, Buildings, Construction par l'architecte Christopher Alexander et quelques autres. Ce livre présente les modèles de conception dans un contexte architectural, présentant 253 modèles qui forment collectivement ce que les auteurs ont appelé un langage de modèles .

L'ironie des modèles de design

Bien que les modèles de conception utilisés pour la conception de logiciels remontent à A Pattern Language , ce travail architectural a été influencé par le langage alors émergent pour décrire la programmation et la conception informatiques.

Le concept d'un langage de modèles est apparu par la suite dans la conception de systèmes centrés sur l'utilisateur de Donald Norman et Stephen Draper , qui a été publié en 1986. Ce livre a suggéré l'application des langages de modèles à la conception d'interaction , qui est la pratique de concevoir des produits, environnements et systèmes numériques interactifs et services à usage humain.

Pendant ce temps, Kent Beck et Ward Cunningham avaient commencé à étudier les modèles et leur applicabilité à la conception de logiciels. En 1987, ils ont utilisé une série de modèles de conception pour aider le groupe de systèmes de test de semi-conducteurs de Tektronix, qui avait du mal à terminer un projet de conception. Beck et Cunningham ont suivi les conseils d'Alexander pour une conception centrée sur l'utilisateur (laisser les représentants des utilisateurs du projet déterminer le résultat de la conception) tout en leur fournissant des modèles de conception pour faciliter le travail.

Erich Gamma a également réalisé l'importance des modèles de conception récurrents tout en travaillant sur sa thèse de doctorat. Il pensait que les modèles de conception pourraient faciliter la tâche d'écrire des logiciels orientés objet réutilisables et s'est demandé comment les documenter et les communiquer efficacement. Avant la Conférence européenne de 1991 sur la programmation orientée objet, Gamma et Richard Helm ont commencé à cataloguer des modèles.

Lors d'un atelier OOPSLA tenu en 1991, Gamma et Helm ont été rejoints par Ralph Johnson et John Vlissides. Ce Gang of Four (GoF), comme on les appelait par la suite, a continué à écrire les modèles de conception populaires : éléments de logiciels orientés objet réutilisables , qui documente 23 modèles de conception dans trois catégories.

L'évolution moderne des modèles de conception

Les modèles de conception ont continué d'évoluer depuis le livre original du GoF, d'autant plus que les développeurs de logiciels ont été confrontés à de nouveaux défis liés à l'évolution des exigences matérielles et applicatives.

En 1994, une organisation à but non lucratif basée aux États-Unis, connue sous le nom de Hillside Group, a inauguré Pattern Languages ​​of Programs , un groupe de conférences annuelles dont le but est de développer et d'affiner l'art des modèles de conception de logiciels. Ces conférences en cours ont donné de nombreux exemples de modèles de conception spécifiques à un domaine. Par exemple, des modèles de conception dans un contexte de concurrence.

Christopher Alexander chez OOPSLA

Le discours d'ouverture de l'OOPSLA 96 a été prononcé par l'architecte Christopher Alexander. Alexander a réfléchi sur son travail et sur la façon dont la communauté de la programmation orientée objet avait frappé et raté la cible en adoptant et en adaptant ses idées sur les langages de modèles aux logiciels. Vous pouvez lire le discours d'Alexandre dans son intégralité: «Les origines de la théorie des modèles: l'avenir de la théorie et la génération d'un monde vivant».

En 1998, Mark Grand a publié Patterns en Java . Ce livre comprenait des modèles de conception non trouvés dans le livre du GoF, y compris des modèles de concurrence. Grand a également utilisé le langage de modélisation unifié (UML) pour décrire les modèles de conception et leurs solutions. Les exemples du livre ont été exprimés et décrits dans le langage Java.

Modèles de conception de logiciels par classification

Les modèles de conception de logiciels modernes sont globalement classés en quatre catégories en fonction de leur utilisation: création, structure, comportement et concurrence. Je vais discuter de chaque catégorie, puis énumérer et décrire certains des modèles importants pour chacun.

Autres types de modèles de conception

Si vous pensez qu'il existe d'autres types de modèles, vous avez raison. Un article ultérieur de cette série abordera d'autres types de modèles de conception: les modèles d'interaction, d'architecture, d'organisation et de communication / présentation.

Modèles créatifs

Un modèle de création fait abstraction du processus d'instanciation, séparant la façon dont les objets sont créés, composés et représentés du code qui en dépend. Les modèles de création de classe utilisent l'héritage pour faire varier les classes qui sont instanciées, et les modèles de création d'objet délèguent l'instanciation à d'autres objets.

  • Usine abstraite : ce modèle fournit une interface pour encapsuler un groupe d'usines individuelles qui ont un thème commun sans spécifier leurs classes concrètes.
  • Générateur : sépare la construction d'un objet complexe de sa représentation, permettant au même processus de construction de créer diverses représentations. L'abstrait des étapes de construction d'objets permet différentes implémentations des étapes pour construire différentes représentations des objets.
  • Méthode Factory : définit une interface pour créer un objet, mais laisse les sous-classes décider quelle classe instancier. Ce modèle permet à une classe de différer l'instanciation aux sous-classes. L'injection de dépendance est un modèle connexe. (Voir Ressources.)
  • Initialisation paresseuse : ce modèle nous donne un moyen de retarder la création d'objet, la recherche dans la base de données ou un autre processus coûteux jusqu'à la première fois que le résultat est requis.
  • Multiton : développe le concept de singleton pour gérer une carte d'instances de classe nommées sous forme de paires clé-valeur et fournit un point d'accès global à celles-ci.
  • Pool d'objets : conservez un ensemble d'objets initialisés prêts à être utilisés, plutôt que d'être alloués et détruits à la demande. Le but est d'éviter l'acquisition et la récupération coûteuses des ressources en recyclant les objets qui ne sont plus utilisés.
  • Prototype : spécifie les types d'objets à créer à l'aide d'une instance prototypique, puis créez de nouveaux objets en copiant ce prototype. L'instance prototypique est clonée pour générer de nouveaux objets.
  • L'acquisition de ressources est l'initialisation : ce modèle garantit que les ressources sont automatiquement et correctement initialisées et récupérées en les liant à la durée de vie des objets appropriés. Les ressources sont acquises lors de l'initialisation des objets, lorsqu'il n'y a aucune chance qu'elles soient utilisées avant qu'elles ne soient disponibles, et libérées avec la destruction des mêmes objets, ce qui est garanti même en cas d'erreurs.
  • Singleton : garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à cette instance.