Comment ne pas comprendre les principes de développement d'une architecture SOLIDE

Il y a un problème avec la description et l'interprétation des principes de développement de l'architecture SOLID (auteur de Robert Martin). De nombreuses sources donnent leur définition et même des exemples de leur utilisation. En les étudiant et en essayant de m'utiliser moi-même, je me suis constamment surpris à penser qu'il n'y avait pas assez d'explications sur la magie de leur application. Et essayer de voir les engrenages internes, de comprendre - et pour moi cela signifie de se souvenir - les a disposés dans leurs «étagères à termes». Eh bien, si cela sera utile à quelqu'un d'autre.


image


Nous procédons à «jongler avec les étagères» de l'approche de conception ci-dessus.


Principe de responsabilité unique (PRS) principe de responsabilité unique


Un morceau de code ne devrait changer que lors de la mise en œuvre d'un objectif. Si une section de code implémente deux tâches et change pour des utilisations différentes, vous devez dupliquer cette section dans une instance pour chaque objectif. Ceci est très important car il nécessite une dérogation au principe généralement accepté d'éliminer les doubles emplois.


Le but de ce principe est d'éliminer les erreurs implicites introduites du fait que les invariants suivants existent pour le développement d'une section de code, d'une procédure, d'une classe, d'un composant (ci-après, le terme [composant] est utilisé pour combiner ces concepts):


  • [1] correctement écrit [composant] est nécessairement utilisé et le plus souvent plusieurs fois,
  • [2] à chaque lieu d'utilisation, [composant] devrait maintenir un comportement cohérent conduisant à un résultat reproductible,
  • [3] lors de l'utilisation du [composant] à plusieurs endroits, le résultat doit satisfaire chaque lieu d'utilisation,
  • si un changement de [composant] est requis pour l'un des lieux d'utilisation et que le comportement précédent de [composant] est requis pour un autre lieu d'utilisation, il est nécessaire de créer une copie de [composant] puis de la modifier (ou de généraliser [composant] avec des paramètres supplémentaires qui fournissent un comportement différent),
  • s'il existe des lieux d'utilisation du [composant] qui ne sont pas importants pour la tâche actuelle résolue par le programmeur, alors il est très facile pour lui d'oublier de vérifier la compatibilité avec ces lieux d'utilisation des modifications apportées à ce [composant].


    Par conséquent, tous les lieux d'utilisation doivent être situés dans la zone [Responsabilité unique] d'une seule responsabilité, c'est-à-dire être modifiés et pris en compte à la fois pour tout problème résolu par le programmeur).


    Le principe s'applique à la fois à une section de code et à un composant, une bibliothèque, un programme, un ensemble de programmes utilisés à plusieurs endroits.


    De nombreuses sources donnent un exemple d'une classe avec une seule "fonction" comme idéal de SRP et la classe de "l'objet divin", combinant toutes les fonctions de l'application, comme anti-modèle. Une classe à mon humble avis avec une seule «fonction» est une exigence pour l'optimisation prématurée de l'architecture de code, ce qui incite à écrire de nombreuses classes (entités de code) à partir de zéro, tout en oubliant que l'absence de plus d'un lieu d'utilisation permet au programmeur d'évaluer rapidement une petite quantité située localement (dans une classe) code interactif que d'analyser les relations extérieures d'entités de code disparates responsables de leur "fonction". Un `` objet divin '' pour une petite application n'est pas non plus un crime fort - il vous permet de commencer le développement: sélectionnez toutes les entités nécessaires et, en les écrivant côte à côte, séparées des objets externes de la bibliothèque standard et des modules externes (créez une cellule vivante et séparez-la avec une membrane). Dans le processus de croissance et de développement du projet, il existe de nombreuses méthodes qui aident à suivre le SRP, l'une d'entre elles est la division en classes et minimisant le nombre de "fonctions" dont chaque classe est responsable (division cellulaire et leur spécialisation dans le corps).


    Ici, je voudrais écrire un ensemble de techniques pour maintenir SRP, mais ce travail n'est pas encore terminé (j'espère que "les mains atteignent"). Des zones évidentes où vous pouvez rechercher ces astuces:


  • modèles de conception;
  • en utilisant différentes branches de composants spécialisés, par opposition à la création d'un composant qui satisfait toutes les méthodes d'application (fork sur GitHub).

Principe ouvert-fermé (OCP) Principe ouvert / fermé


Il est optimal de planifier le développement du code de sorte que pour que le programmeur implémente de nouvelles tâches, il est nécessaire d'ajouter du nouveau code, tandis que l'ancien code n'a pas besoin de modifications. Le code doit être ouvert (Open) pour ajouter et fermé (Closed) pour changer.


Le but de ce principe est de minimiser les coûts de main-d'œuvre et d'éliminer les erreurs implicites introduites en raison des invariants suivants en développement:


  • [1], [2], [3] décrits précédemment,
  • pour implémenter une nouvelle tâche, le programmeur peut ajouter de nouveaux [composants] ou changer le comportement des anciens [composants],
  • l'ajout de [composant] nécessite une vérification sur le lieu de la nouvelle utilisation et coûte du temps au programmeur
  • le changement de comportement du [composant] causé par la nouvelle tâche nécessite une vérification sur le lieu de la nouvelle utilisation et dans tous les lieux de l'ancienne utilisation, ce qui entraîne également la consommation de temps du programmeur, et dans le cas du [composant] publié, le travail de tous les programmeurs qui ont utilisé le [composant].
  • il est conseillé de choisir une option pour implémenter une nouvelle tâche tout en minimisant le temps passé par le programmeur.


    Le plus souvent dans la pratique du développement logiciel, le coût de l'ajout est bien inférieur au coût du changement, ce qui rend évident l'utilisation du principe [Open-Closed]. Dans le même temps, il existe de nombreuses techniques pour maintenir l'architecture du programme dans un état où la mise en œuvre d'une nouvelle tâche se résume à l'ajout de [composants] uniquement. Ce travail avec l'architecture nécessite également du temps pour un programmeur, mais la pratique dans les grands projets montre beaucoup moins que l'utilisation de l'approche consistant à modifier les anciennes procédures. Et, bien sûr, cette description du développement est l'idéalisation. Il n'y a presque aucune implémentation de la tâche en ajoutant ou en changeant simplement. Dans la vie réelle, un mélange de ces approches est utilisé, mais OCP met l'accent sur l'avantage d'utiliser l'approche add.


    Et ici, je voudrais écrire un ensemble de techniques pour maintenir OCP. Des zones évidentes où vous pouvez rechercher ces astuces:


  • modèles de conception;
  • bibliothèques dll et options pour leur distribution, mise à jour et développement de fonctionnalités;
  • développement des bibliothèques COM et des objets qu'elles contiennent;
  • développement de langages de programmation et prise en charge de code précédemment écrit;
  • développer le système législatif de l'État.

Principe de substitution de Liskov (LSP) Barbara Principe de substitution de Liskov


Ce principe limite l'utilisation de l'extension de l'interface de base [base] à l'implémentation, en précisant que chaque implémentation de l'interface de base doit avoir un comportement d'interface de base. Dans le même temps, l'interface de base corrige le comportement attendu dans les lieux d'utilisation. Et la présence dans le comportement d'implémentation d'une différence par rapport au comportement attendu, fixé par l'interface de base, conduira à la possibilité de violation de l'invariant [2].


Ce principe est basé et affine la technique de conception basée sur l'abstraction. Dans cette approche, une abstraction est introduite - certaines propriétés de base et caractéristiques de comportement de nombreuses situations sont fixées. Par exemple, [composant-procédure] "Déplacer vers la position précédente" pour les situations: "Curseur dans le texte", "Livre sur une étagère", "Élément dans un tableau", "Pieds en danse", etc. Et affecté à ce [composant] ( souvent par l'expérience quotidienne et sans formalisation) quelques prérequis et comportements, par exemple: «La présence d'un objet mobile», «Répéter plusieurs fois», «Présence de l'ordre des éléments», «Présence de positions fixes des éléments». LSP requiert que lors de l'ajout d'une nouvelle situation d'utilisation pour [composant] toutes les conditions préalables et limitations de la base soient remplies. Et la situation «les grains dans une canne à sucre» ne peut pas être décrite par cette abstraction, bien que les grains, bien sûr, aient une position, il y a des positions dans lesquelles les grains ont été auparavant, et il est possible de les déplacer - il n'y a pas de positions fixes d'éléments.


Le but de ce principe est d'éliminer les erreurs implicites introduites en raison des invariants suivants en cours de développement:


  • [1], [2], [3] décrits précédemment,
  • la [procédure] de base décrit un comportement utile dans un grand nombre de situations, fixant les contraintes nécessaires à son applicabilité,
  • la [procédure] élaborée pour la mise en œuvre de la base doit remplir toutes ses limites, y compris celles implicites et rigoureuses (fournies de manière informelle).


    Très souvent, un exemple avec un Rectangle ([base]) et un Carré (implémentation) est donné pour décrire ce principe. class CSquare : public CRectangle situation class CSquare : public CRectangle . Dans [base], les opérations de travail avec la largeur et la hauteur (Set (Get) Width, Set (Get) Height) sont introduites. Dans l'implémentation de CSquare, ces opérations Set sont obligées de modifier les deux tailles de l'objet. J'ai toujours manqué l'explication selon laquelle la restriction suivante est définie "de façon informelle" dans [la base]: "la possibilité d'utiliser Largeur, Hauteur indépendamment". Dans l'implémentation CSquare, il est violé, et dans les lieux d'utilisation, une simple séquence d'actions basée sur l'utilisation de cette indépendance: r.SetWidth(r.GetWidth()*2); r.SetHeight(r.GetHeight()*2) r.SetWidth(r.GetWidth()*2); r.SetHeight(r.GetHeight()*2) - pour l'implémentation, CSquare augmentera les deux tailles de 4 fois, au lieu de 2 fois pour CRectangle.


    À mon humble avis, ce principe indique la difficulté de suivre de telles contraintes informelles qui, avec une grande utilité et une fréquence élevée d'utilisation de l'approche de développement "mise en œuvre de base", nécessitent une attention particulière.



Principe de séparation des interfaces (ISP) Principe de séparation des interfaces; Principe d'inversion de dépendance (DIP) Principe d'inversion de dépendance


Ces deux principes sont très proches dans le domaine de leurs exigences. Les deux impliquent implicitement l'utilité d'utiliser la plus petite interface de base possible, comme un outil pour l'interaction de deux [composants]: "client" et "serveur" - ces noms sont choisis simplement pour l'identification. Dans ce cas, les informations générales utilisées par les [composants] sont concentrées dans l'interface de base. Un [composant] ("serveur") implémente l'implémentation de l'interface de base, l'autre [composant] ("client") fait référence à cette implémentation.


L'objectif de ces principes est de minimiser les dépendances des composants, permettant des modifications indépendantes de leur code s'il ne modifie pas l'interface sous-jacente. L'indépendance des changements de composants réduit la complexité et la main-d'œuvre si les composants répondent aux exigences du principe SRP. Une approche similaire est possible car les invariants suivants existent en cours de développement:


  • [1], [2], [3] décrits précédemment,
  • chaque [composant] inhérent à son comportement forme les limites de son utilisation,
  • dans chaque lieu d'utilisation du [composant] toutes ses restrictions peuvent être impliquées,
  • la conséquence [composante] de base de la définition a moins de complexité et le nombre de restrictions que la mise en œuvre [composante],
  • tout changement dans [composant] modifie ses limites et nécessite une vérification de tous les lieux d'utilisation, ce qui entraîne une perte de temps pour un programmeur,
  • les lieux d'utilisation du [composant] de base ne nécessitent pas de vérification après avoir modifié l'implémentation du [composant].


    Il est clair qu’il est conseillé de minimiser la «taille» de l’interface de base en supprimant les fonctionnalités et les restrictions inutilisées, limitant ainsi la mise en œuvre des [composants] par le principe de (LSP) moins


    Le principe du FAI souligne la nécessité d'une séparation (ségrégation) de l'interface du "serveur", si toutes ses fonctionnalités publiées ne sont pas utilisées par ce "client". Dans ce cas, seule la [base] requise par le client est allouée et la minimisation des informations restrictives conjointes est assurée.


    Et ici, je voudrais écrire un ensemble de techniques pour maintenir DIP. Des zones évidentes où vous pouvez rechercher ces astuces:


  • séparation de la description de classe en parties publiques et privées (et autres principes de la POO),
  • description de l'interaction avec une bibliothèque dynamique avec un ensemble limité de fonctions et de descripteurs d'objets,
  • utiliser un classeur comme interface pour accéder à une bibliothèque de livres

Revenant à la rubrique, je vais expliquer pourquoi "ne pas comprendre" est sélectionné. La négation est ajoutée afin de souligner par erreur la règle utile qui souffre depuis longtemps et très à mon humble avis. Il vaut mieux ne pas comprendre et donc ne pas utiliser la technologie, que de mal comprendre, de croire en elle, de dépenser vos ressources pour utiliser la technologie et, par conséquent, de ne pas avoir d’épuisement utile, sauf la complaisance et la possibilité de se vanter de la participation à une technologie à la mode.


Merci de votre attention.


Les références


Source: https://habr.com/ru/post/fr444932/


All Articles