diff --git a/content/projet_programmation/9_principes_SOLID/index.md b/content/projet_programmation/9_principes_SOLID/index.md new file mode 100644 index 0000000..d98e176 --- /dev/null +++ b/content/projet_programmation/9_principes_SOLID/index.md @@ -0,0 +1,185 @@ +--- +title: "PdP: principes SOLID" +date: 2024-03-28 +tags: ["besoins", "UML", "developpement logiciel"] +categories: ["Projet de programmation", "Cours"] +--- + +C'est un ensemble de principe souvent associés à la programmation objet. Ils +s'appliquent cependant à toute forme d'architecture. Il sont au nombre de 5 : + + * principe d'inversion des dépendances que nousa vons déjà vu précédement; + * principe de responsablité unique; + * principe de ségrégation d'interfaces; + * principe ouvert/fermé + * principe de substitution (de *Liskov*) + +## Inversion des dépendances + +Utiliser des interfaces comme intermédiaire afin d'inverser les dépendances. LEs +interfaces utilisées comme intermédiaires permettent de faire écran (masquer +l'implementation). Elles permettent aussi de proteger les composants de la +diffusion. + +Dans sa **version stricte**, les interfaces ne portent que ce qui est +nécessaire, mais ça peut être lourd (multiplication des interfaces). Il est donc +plus courant (et désirable) de généraliser les interfaces. + +## Responsablité + +Il est question de décomposer pour maîtriser, mais aussi pour limiter les +responsabilité des composants. + +La **responsabilité d'un composant** est un rôle, un objectif, un comportement, +un ensemble de services qu'il fournit et dont il est le garant. Il faut alors +favoriser la contruction de composants avec un nombre réduit de responsabilité. + +Dans sa version radicalisé (Martin, 2003), il est même question de favoriser les +composant avec **une seule responsabilité**. Mais attention, il ne faut pas +appliquer ce principe jusqu'à emietter complètement une architecture (code +*ravioli*). + +Une responsabilité ne se résume pas à une seule fonction (ou une seule action) +mais plutôt à un ensemble cohérent de fonctions / actions. Voici les critères +permettant de la caractériser une responsabilité unique: + + * être décrite simplement en lien avec le domaine du programme (et en langage + naturel); + * être associé à un haut niveau de cohésion dans le composant (ce qui signifie + plus de dépendance entre ses éléments); + * être identifiée par rapport à la manière récurrente dont le composant est + utilisé par les autres composants (féquement utilisé pour **tout** ce qu'il + propose); + * être associé à sa raison principale la plus probable d'être modifiée (une + seule raison principale d'évoluer). + +### Son anti-pattern juré: le **blob** + +C'est l'inverse de la responsabilité unique : un composant fourre-tout qui +centralise beaucoup de responsabilité différentes et entourré de composants qui +ne contiennent que des données ou des processus simples. + + +## Ségrégation d'interfaces + +Nous avons vu que la taille d'une dépendance entre deux composants est +caractérisée par la quantité d'information qui transite entre eux. nous avonx vu +que nous devons avant tout les minimiser. + +Le principe de ségrégation des interfaces est le principe selon lequel il faut +favoriser la construction de composants dont les dépendances correspondent à +ce qu'ils utilisent réellement (ni plus, ni mois). Nous parlons ici d'interfaces +au sens large, mais au sens d'interfaces de classes. + +Il est donc parfois nécessaire de décomposer pour l'appliquer, quitte à +recomposer ensuite. + +## Ouvert / Fermer + +Pour tout logiciel, des transformations et des évolutions seront nécessaire et +appliquées. Il sont dit *ouverts* + +Il est aussi préférable de ne pas toucher à ce qui fonctionne bien et est +utilisé. Ceci pourrait pertuber un écosystème fragile. C'est la partie *fermée*. +Nous préservons ainsi son fonctionnement, les liens entre les composants, les +interfaces, les tests etc. + +Pour faire évoluer notre logiciel snas pour autant toucher aux composants +fonctionnels, nous avons alors plusieurs pistes: + + * utiliser l'héritage et ainsi ne pas toucher au composant de base fonctionnel; + * utiliser les interfaces pour, par exemple, de nouvelles implémentations + (réalisations multiples) + * utiliser la généricité, l'usage de composants paramétrés permettant de les + adaptés selon l'instanciation de ces partamètres tout en les préservant; + * utiliser les plugins. + +Il faut donc favoriser la construction de composants ouverts-fermés par rapport +aux changements les plus probables, ouverts à l'extension et fermé à la +modification. Toute la difficulté ici est d'obtenir des composants +ouverts-fermés facile à faire évoluer. + +## Substitution de Liskov + +Dans une architecture logicielle, il doit être possible d'effectuer des +remplacement localiser de composants de manière à préserver son fonctionnement. +La **substitution** est le remplacement d'un élément par un autre similaire +indépendement du contexte (*context-free*) et sans induire d'autres changements. +Il faut alors passer par une spécfication, ainsi la variation se fait sur la +manière de l'implémenter. + +Il faut alors favoriser la **construction de composants spécifiés** de manière à +pouvoir les substituer. Il faut alors faireparticulièrement attention à la +**précision de la spécification** en fonction du contexte. + +### substitution par sous-typage + +Un composant substituant sous une spécification \\(S\\) peuvent implémenter plus +de propriété que requis par \\(S\\) sans pour autant metter à mal les propriétés +de substitution. En clair *Qui peut le plus peut le moins*. + +Une spécification \\(S'\\) est compatible avec une autre \\(S\\) (noté +\\(S' <: S\\)) si on peut déduire toutes les propriété de \\(S\\) à partir de +\\(S'\\) et donc si \\(S' \implies S\\). + +De manière générale, lorsque \\(S' <: S\\) tout composants qui implémente +\\(S'\\) implémente aussi \\(S\\). Ainsi si on considère les spécifications +comme des types, la compatibilité définis ce qu'est le **sous-typage**. + +L'application de la substitution requiert une bonne connaissance de ce qui +engendre des compatibilités. + +### compatibilité + +La compatibilité entre **spécifications de fonctions** dépend en particulier de +leurs domaines d'entrée et de sorties. La compatibilité entre spécfications de +fonctions est donc : + +\\[ +(In' \rightarrow Out') <: (In \rightarrow Out')\\\ +\text{si } In <: In' \\\ +\text{et } Out' <: Out +\\] + +Donc: + + * \\(In'\\) a un ensemble d'instances **égal ou plus grand** que \\(In\\); + * \\(Out'\\) a un ensemble d'instance **égal ou plus petit** que \\(Out\\); + + Cette règles relatives aux fonctions peuvent se généraliser **aux + conditions**: + + * les **préconditions** \\(Pre\\) d'une spécification \\(S\\) sont les + propriétés de notre spécification qui doivent être implémentées par un + composant pour qu'ils soit exécuté, appliqué, activé. + * les **postconditions** \\(Post\\) d'une spécification \\(S\\) sont les + propriétés de notre spécification qui doivent être implémentées par les + résultats, les services rendus, les effets obtenus d'un composant. + +\\[ +(Pre', Post') <: (Pre, Post')\\\ +\text{si } Pre <: Pre' \\\ +\text{et } Post' <: Post +\\] + +autrement dit: + + * les préconditions sont plus faible ou égale: **contravariance**; + * les postconditions sont plus forte ou égale: **covariance** + +### Vérification des substitutions + +Liskov introduit des bugs s'il n'est pas bien appliqué. La compréhension est +donc essentielle sinon des bugs liés aux intentions feront leur apparition, et +ils sont souvent difficile à trouver. + +### Principe de substitution de Liskov + +Sans une architecture logicielle, il faut favoriser la construction de +composants spécifiés au niveau adéquat de précision de manière à ce qu'ils +soient substituables sous leur spécification de manière satisfaisante dans leur +contexte de programmation. + +Il faudra considérer alors le bon choix de précision des spécifications et la +vérification des spécifications, des compatibilités ainsi que des aproximations +qui y sont associés.