feat: add SOLID principles
This commit is contained in:
parent
04b13df5dc
commit
cde393c033
1 changed files with 185 additions and 0 deletions
185
content/projet_programmation/9_principes_SOLID/index.md
Normal file
185
content/projet_programmation/9_principes_SOLID/index.md
Normal file
|
@ -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.
|
Loading…
Add table
Add a link
Reference in a new issue