Introduction
Qui a dit que la modélisation de nos systèmes était de tout repos ?
Dans cet article, nous allons explorer un cas concret inspiré du domaine de l’énergie :
la modélisation des sites équipés de compteurs électriques.
Nous allons ici détailler le chemin de pensée de notre équipe lors du développement d'une nouvelle fonctionnalité.
D'un modèle très simple, en passant par l'héritage et la composition nous terminerons notre voyage sur le Pattern Composite.
1. Définir le besoin métier
Dans notre système, nous avons des sites (des maisons, des écoles, des mairies...). Et nous aimerions connaître la consommation d'énergie de ces sites pour une période donnée et les comparer aux sites de même type. Par exemple pour le calcul de la consommation moyenne par foyer pour une étude INSEE.
Pour faire simple, nous avons un site qui est une entité consommatrice d’électricité qui possède ces attributs :
- un nom
- un numéro de compteur (identifiant unique)
- un type (maison, école, entrepôt…)
- une consommation d'énergie (sur une période donnée)
Voici donc un premier modèle :
Cette modélisation suffit tant que nous gérons des sites individuels, mais le métier en a décidé autrement.
2. Nouveau besoin métier : regroupement de sites
En tant que gestionnaire énergétique de la ville de Rennes,
Je veux accéder à la consommation des différents quartiers,
Afin de comparer les zones et orienter les travaux urbains.
En lisant ce besoin, une idée rapide nous vient :
“Remplaçons le numéro de compteur unique par une liste de compteurs afin de les regrouper !”
Mais ce modèle atteint rapidement ses limites :
- Tous les compteurs d’un site seraient forcés de partager les mêmes caractéristiques (Le nom d'une maison, n'est pas le même que celui du quatier)
- On ne distingue plus un site individuel d’un regroupement de sites.
Le vocabulaire du métier évolue aussi (ubiquitous language) : on parle désormais d'un lot (un groupement de sites), qui est une entité distincte qu’on ne représente pas du tout ici.
On manque également d'exemples pour bien comprendre l'intention métier. Prenons celui-ci déjà structuré :
Scénario: Calcul de la consommation totale du quartier
Étant donné les sites suivants dans le quartier "Beaulieu" :
| Nom | Consommation (kWh) |
| Bibliothèque Universitaire| 6000 |
| Restaurant Universitaire | 15000 |
Quand je demande la consommation du quartier "Beaulieu"
Alors la consommation totale est 21000 kWh
3. Tentative avec l’héritage
À ce moment-là, on se dit :
“Finalement, une maison ou un quartier, c’est un peu la même chose, non ?
Les deux consomment de l’électricité, les deux ont un nom. Que ce soit une maison qui possède une adresse ou encore le nom d'un quartier.
On pourrait peut-être modéliser ça de la même manière !”
Faisons hériter le Lot du Site.
C'est pratique un Lot peut etre utilisé partout où un site est attendu.
Mais cette modélisation est trompeuse.
Un Lot n’est pas une spécialisation d'un site.
Ce n’est pas un cas particulier de site, mais bien un agrégat de sites.Une maison ne devient pas un immeuble en héritant d’une autre. Et un immeuble n’est pas une maison améliorée.
Et enfin plus technique, on viole le principe de substitution de Liskov.
En effet la violation se produit lorsque la classe enfant modifie le contrat de comportement implicite du parent.
Par exemple quand vous allez au marché acheter des fruits; pour calculer le prix des clémentines on va les peser. On ne pourra pas naturellement remplacer certaines clémentines de notre balance par des kiwis lors de la pesée car le prix du kiwi lui ne dépend pas du poids mais de la quantité. C'est cette différence de comportement dans la pesée qui casse la substitution de liskov car on suppose que la méthode de calcul est forcément liée au poids.
C'est pareil si le Lot hérite du Site, le comportement attendu d’un site (retourner un numéro de compteur, un type, etc.) ne peut pas s’appliquer à un Lot sans introduire des incohérences ou des contournements.
4. Revenir à la réalité pour guider la modélisation
Plutôt que de forcer la logique technique, regardons ce que dit la réalité :
- Peut-on regrouper plusieurs maisons ? Oui, cela s’appelle un quartier.
- Et si nous empilons les maisons (et qu'on les appelles appartements à la place 😉) ? Cela devient un immeuble.
Ce qu’on cherche à modéliser, c’est donc une structure composée de consommateurs, où chaque entité (simple ou composée) reste un consommateur d'électricité.
La composition permet déjà d’associer des objets différents (par exemple, un lot qui contient des sites).
Le pattern Composite, lui, pousse cette idée plus loin : il permet de composer des objets du même type abstrait, afin de manipuler de manière uniforme les entités simples et celles qui en regroupent plusieurs.
C’est exactement ce que permet le pattern Composite.
5. Le pattern Composite à la rescousse
Avec ce pattern, on introduit une abstraction Consommateur que peuvent implémenter :
- les entités simples comme
Site - les entités composites comme
Lot
Chaque Lot peut contenir des Site, mais aussi d’autres Lot. Le tout est considéré comme un Consommateur. Ainsi, notre système peut manipuler les entités simples et composées de façon uniforme.
Pour concrétiser le schéma, voici comment cela se traduit en Java.
Le Consommateur (le composant) :
public interface Consommateur {
int getConsommation();
}
Ensuite le Site (la feuille), qui implémente simplement ce contrat :
public class Site implements Consommateur {
private String nom;
private String numeroCompteur;
private String type;
private int consommation;
public Site(String nom, String numeroCompteur, String type, int consommation) {
this.nom = nom;
this.numeroCompteur = numeroCompteur;
this.type = type;
this.consommation = consommation;
}
@Override
public int getConsommation() {
return this.consommation;
}
}
Enfin le Lot (le composite):
import java.util.ArrayList;
import java.util.List;
public class Lot implements Consommateur {
private String nom;
// La liste contient des interfaces : elle accepte donc des Sites OU des Lots
private List<Consommateur> enfants = new ArrayList<>();
public Lot(String nom) {
this.nom = nom;
}
public void ajouter(Consommateur c) {
enfants.add(c);
}
@Override
public int getConsommation() {
// On délègue le calcul à chaque enfant, qu'il soit un Site ou un autre Lot
return enfants.stream()
.mapToInt(Consommateur::getConsommation)
.sum();
}
}
Pour reprendre notre exemple, voici son utilisation :
public class Main {
public static void main(String[] args) {
// Des sites simples
Site s1 = new Site("Bibliothèque", "12345678912345", "batiment_publique", 6000);
Site s2 = new Site("Restaurant U", "012435633785", "batiment_publique", 15000);
// Un lot (Quartier)
Lot quartierBeaulieu = new Lot("Beaulieu");
quartierBeaulieu.ajouter(s1);
quartierBeaulieu.ajouter(s2);
// Un autre lot qui contient le premier ! (Ville)
Lot villeRennes = new Lot("Rennes");
villeRennes.ajouter(quartierBeaulieu); // On ajoute un Lot dans un Lot
villeRennes.ajouter(new Site("Mairie", "12345678900045", "batiment_publique", 2000));
// Le client n'a pas besoin de savoir la structure interne
System.out.println("Conso totale : " + villeRennes.getConsommation());
// Affiche : 23000
}
}
6. Ce que l’on y gagne
- Une modélisation plus proche de la réalité
- Une extensibilité naturelle du modèle à travers le Consommateur
- Une logique métier plus claire
- Un code plus simple et plus propre
7. Conclusion
Finalement, notre cheminement de pensée est passé par pas mal d'état, nous avions besoin de comprendre le métier, d'y comprendre le sens, d'avoir aussi des exemples sur lesquels nous appuyer. #BDD
Nous savons qu'une modélisation du système n'est jamais figée dans le temps, elle évolue. Dans notre cas, ni l'héritage ni la simple composition pouvaient répondre au problème. Le pattern Composite était le choix le plus cohérent. Il nous a permis de représenter des structures hiérarchiques dans lesquelles les objets simples et composés pouvaient être traités de manière homogène.







Top comments (0)