185 lines
7.7 KiB
Markdown
185 lines
7.7 KiB
Markdown
---
|
|
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.
|