diff --git a/content/projet_programmation/7_architecture_logicielle/index.md b/content/projet_programmation/7_architecture_logicielle/index.md index 6fda3b7..da10540 100644 --- a/content/projet_programmation/7_architecture_logicielle/index.md +++ b/content/projet_programmation/7_architecture_logicielle/index.md @@ -1,57 +1,241 @@ --- -title: "PdP: Architecture loficielle" +title: "PdP: Architecture logicielle" date: 2024-03-14 tags: ["besoins", "UML", "developpement logiciel"] categories: ["Projet de programmation", "Cours"] --- +## Introduction + +une architecture logicielle est une structure composite basée sur une +décomposition de composants distincts et une ensemble de dépendance et/ou +d'interaction entre ces composants. + +La finalité est de répondre un ensemble de besoins logiciels et d'offrir un +ensemble de services. + +> Engineering is all about breaking down big problems into smaller ones +> and putting the solutions for those problems back together + +Boswell and Fouchet, 2011 + +Il est donc question ici de maitriser la taille et la complexité pour assurer le +**passage à l'échelle** : décomposer pour maîtriser et faire évoluer. En effet +les architectures logicielles doivent aussi être modifiables, extensible et +évolutives. Les composants et leurs dépendances doivent **permetttre et +faciliter le changement**. + +## Dépendances et composants + +Un composant, aussi appelé aussi *bloc de construction* est tout élément +syntaxique qui participe à la structure de son architecture : les intructions, +les expressions, fonctions, classes, modules, paquetages, etc. + +Un composant \\(C_1\\) est dépendant de \\(C_2\\) si \\(C_1\\) utilise \\(C_2\\) +dans sa définition ou implémentation. + +![Représentation des dépendences](./imgs/dependence_c1_c2.svg) + +Voici comment représenter les dépendances entre deux classes, notez le sens de +la flèche, elle pointe vers \\(C_2\\) : *"dépends de". + +Nous avons trois grande famille de dépendances: + + * Les **utilisations simples** d'éléments de composants. Par exemple une + fonction \\(f_1\\) appelle une autre fonction \\(f_2\\); + * Les **associations** entre composants par imbrications. Par exemple une + classe \\(C_1\\) inclut un attribut de la classe \\(C_2\\); + * Les **inclusions** ou **héritages** entre composants par réutilisation. Par + exemple un sources C \\(s_1\\) inclut via `#include` un autre fichier + \\(s_2\\). + +### Taille -- flux et débit -- des dépendances + +Les dépendances d'une architecture doivent être utilisable, utilisées et utiles. +Elles doivent transmettre que le stricte minimum d'informations et de qualité +suffisante (contrôle et maîtrise). + +La **taille** d'une dépendance entre \\(c_{1}\\) et \\(c_{2}\\) reflète la +quantité d'information qu'elle peut faire transiter ce qui inclus: + + * Le nombre d'éléments distincts transmis à \\(c_{1}\\) par \\(c_{2}\\); + * Le volume des éléments distincts transmis à \\(c_{1}\\) par \\(c_{2}\\); + * La complexité des éléments distincts transmis à \\(c_{1}\\) par \\(c_{2}\\). + +Cette mesure n'as pas vocation être précise. + +Dasn une architecture logicielle, il faut favoriser la définition d'une +dependance **avec une taille minimale** + +### Diffusion des changements + +Une architecture où les changements dasn les composants sont diffusés aux +autres composants n'est qu'une *architecture factice*. + +Nous devons donc introduire la notion de **force d'une dépendance** entre deux +composants \\(c_{1}\\) et \\(c_{2}\\): + + * Elle est **forte** si une modification dans \\(c_{2}\\) entraine une + modification dans \\(c_{1}\\). + * Elle est faible dans le cas contraire. + +La diffusion des changement va toujours dans le *sens inverse* des dépendances. + +Dans une architecture logicielle, il faut limiter les dépendances forte pour les +changements les plus probables dans cette architecture. On est obligé de se +concentrer sur les changements les plus probables (on ne pourra considérer tous +les changements). Il faut identifier ces changement possibles par le contexte, +les besoins non fonctionnels, l'expérience, etc. + +Plusieurs techniques d'implémentation sont disponible pour appliquer le principe +de diffusion minimisée des changements comme: + + * Les mécanismes de cloisonnement des langages (expression du privée / public); + * En appliquant le principe de *taille minimisé*; + * En utilisant les moyens d'expression d'abstration des langages comme *les + interfaces*. + ### Dépendances et utilisation des interfaces -spécification d'un composant -> description abstraite et rigoureuse de ses -qualités, de sa manière de se comporter, des services qu'il est censé rendre. -Un composant qui satisfait une spécification S est dit réaliser S +La spécification d'un composant représente la description abstraite et rigoureuse de +ses qualités, de sa manière de se comporter, des services qu'il est censé +rendre. Un composant qui satisfait une spécification \\(S\\) est dit *réaliser* +\\(S\\) Utilisation de l'interface au lieu d'une classe ```c++ class C2 { IC1 obj; - // ... + // This is a simple class } interface IC1 { - + // Interface - kind of specification } class C1 implement IC1 { -// ... + // then iplementation. } ``` +![Introduction d'une interface](./imgs/interface.svg) + Ainsi `C2` ne dépends pas de `C1` mais de l'interface `IC1`. En plus de décrire abstraitement les composants à implémenter, elles servent d'intermédiaire dans les dépendances. +Les interfaces contrôlent une part des dépendances fortes, on y spécifie les +éléments les moins susceptibles de changer. Les interfaces ont deux rôles +important dans une architecture logicielle: + + * Décrire de manière abstraite les composants à implémenter; + * Servir d'intermédiaire dans les dépendances et ainsi contrôler la force ou la + faiblesse des dépendances. + +#### Principe d'inversion des dépendances. + +**Une dependance implicite** est une dépendance qui ne se voit pas directement +dans le source d'un programme pas ses appels et ses annotations. Elle est +toujours forte par rapport au changement qui la détermine. + > Dans une architecture logicielle, il est préférable de faire dépendre les -> ocmposants d'interfaces et non les composants implémentés. +> composants d'interfaces et non les composants implémentés. -Principe d'inversion des dépendances. +si nous reprenons le schéma précédent, alors \\(IC_{1}\\) a été écrite par +rapport à l'usage de \\(C_{2}\\) par \\(C_{1}\\), nous pouvons alors en déduire +que \\(C_{2}\\) dépends de la paire \\((C_{1},IC_{1})\\). C'est notre +**inversion de dépendance** -Une dependance implicite -> qui ne se voit pas directement dans le source d;un -programme pas ses appels et ses annotations. Elle est toujours forte par rapport -au chamgement qui la détermine. +### Notion étendue des dépendances + +Nous avons vu des *dépendances explicites* qui se matérialise par les appels de +méthodes, les classes etc. Mais il existe aussi les **dépendances implicite**: +c'est une dépendance qui ne se voit pas directement dans le code source. Elles peuvent prendre des formes très différentes des dépendances explicite par - * la duplication de code - * les similarité de services - * + * La duplication de code; + * les similarité de services offerts par des composants distincts; + * les similarité de structure entre composants distincts. + + Les dépendances implicites sont toujours des dépendances fortes car se sont les + changements qui les définissent. Il est donc préférable de minimiser leur + apparition. (on minimise alors la duplication de code et les similarité par + des techniques de factorisation de code) -LEs archilogicielles sont généralement des structures hiérarchisées. ## Granularité de structure, cohésion et couplage -niveau de granularité défini par +Les architecture logicielles sont généralement des structures hiérarchisées de +composants, c'est une conséquence naturelle du *décomposer pour maîtriser*. +Cette hiérarchisation est généralement simplifiée car elle peut être constituée +essentiellement de niveaux homogène de structure de composants. -Gq et G2 deux niveau de granilarité alirs couplage : ensemble des dépendances -entre les composants de G2 +Son niveau de granularité est déterminé par des composants de même espèce +capable d'inclure des composants du niveau inférieur (s'il y en a). Les niveaux +habituels de granularité offerts par les langages de programmation sont: + + * Les valeurs, données, instructions; + * Les variables, attributs, fonctions (ou méthodes); + * Les modules, classes, objets, + * Les paquetages, clusters, namespace + * Les super-paquetages; + * Les couches. + +Pour des raison de simplification et d'intelligibilité, les dépendances peuvent +se restreindre à ne s'énoncer qu'au sein de chaque niveau de granularité : un +composant \\(C1\\) est explicitement dépendant de \\(C2\\) si \\(C1\\) utilise +\\(C2\\) dans sa définition ou son implémentation et si tous les deux sont au +même niveau de granularité. Les dépendances sont donc à considérer entre +fonctions, entre classes, entre paquetages, etc. + +### Graphes de dépendances + +Les dépendances dans une granularité fixée induisent des **graphes** dont les +sommets sont d'une seule espèce de composants et les arcs des dépendances +explicites. + +Dans la mesure du possible, il faut favoriser la construction de niveau de +granularité homogènes donnant lieu à des graphe de dépendances distincts. C'est +un principe de simplification et d'intelligibilité des architectures logicielles +mais qui n;est pas applicable tout le temps (et c'est parfois souhaitable). Les +langages permettent souvent des moyens de déroger à ce principe (*nested +classes* en Java, lambda expression ...). + +### Suites de granularités, consécutivité + +Les granularités peuvent donc former des suites constitués des éléments que nous +avons évoqué plus haut (données, variables, fonctions etc.). Deux **granularité +consécutives** sont deux granularité qui se suivent dans cette liste, elles +engendre des **graphes consécutifs de dépendances**. + +Il faut veiller à ce que chaque graphes de consécutivité soit plus simple que le +précédent, ils dessinent alors **une pyramide**. Chaque niveaux est une +opportunité de simplifier, synthétiser la description d'une architecture. Bien +entendu il existe des contre-exemples, comme **les classes qui ne contiennent +qu'une seule méthode**. + +Il serait naturel de regrouper un ensemble de méthodes très dépendantes en elles +dans une seule classe, ou encore des classes très dépendances dans un +paquetage. C'est le principe **de simplification min-max**: il faut en sorte +que les composants du niveau de granularité \\(G2\\) englobent le **maximum** de +dépendances de \\(G1\\) tel qu'il reste un **minimum** de dépendances en +composants de \\(G2\\). + +### Couplage et cohésion + +Le couplage représente l'ensemble des dépendances entre composants de \\(G2\\), +la cohésion celles au sein de chacun des composants de \\(G2\\) entre les +éléments de \\(G1\\). + +Si \\(G2\\) est un ensemble de classes et \\(G1\\) un ensemble d'attributs et de +méthodes de ces classes alors: + + * le couplage correspond à l'ensemble des dépendances entre classe de \\(G2\\); + * la cohésion définie pour chaque classe de \\(G2\\) correspond à l'ensemble + des dépendances entre ses attributs et méthodes. + +Pour reprendre notre **principe min-max**: dans une architecture logicielle, pour +toute paire de niveau de granularité, il faut privilégier à la fois **la +cohésion la plus forte** et **le couplage le plus faible**