cours/content/projet_programmation/7_architecture_logicielle/index.md

10 KiB

title date tags categories
PdP: Architecture logicielle 2024-03-14
besoins
UML
developpement logiciel
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

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

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

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

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 composants d'interfaces et non les composants implémentés.

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

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 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)

Granularité de structure, cohésion et couplage

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.

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