Application des principes SOLID

Introduction

Aujourd'hui, une majorité des développeurs développe avec des langages orientés objet.
L'objet est partout, dans tous (ou presque) les langages, et tous les développeurs comprennent intimement ce qu'on entend par de la programmation orientée objet… ou pas.
Après avoir passé de nombreuses années à maintenir et développer du code, on se rend malheureusement compte que les principes du développement objet sont malheureusement soit ignorés, soit mal compris par de nombreux développeurs, ce qui rend assez souvent la maintenance des logiciels au mieux malaisée, au pire impossible.
L'objectif de ce TD est d'appliquer 5 principes de programmations sur un exemplet concret.

Principe SOLID

SOLID est l'acronyme de cinq principes de base (Single Responsibility Principle, Open/Closed Principle, Liskov Substitution Principle, Interface Segregation Principle et Dependency Inversion Principle) que l'on peut appliquer au développement objet.

  • Responsabilité unique (Single responsibility principle) : une classe n’a qu’une seule responsabilité et tous ses services sont en accord avec cette responsabilité, elle ne fait qu’un seul travail. Cela permet d’avoir des classes plus petites, faciles à comprendre, moins complexe. Ainsi la classe n’a qu’une seule raison de changer, si sa responsabilité change.

  • Ouvert/fermé (Open/closed principle) : une classe doit être extensible c’est-à-dire pouvoir avoir une classe fille mais être fermé à la modification, on peut imaginer hérité de classe abstraite ou d’interface pour respecter ce principe facilement.

  • Substitution de Liskov (Liskov Substitution Principle) : un objet doit être remplaçable par un objet qui hérite de celui-ci sans que le programme n’ait d’erreur et les comportements hérité ne doivent pas en provoquer ni ne fasse rien.

  • Ségrégation des interfaces (Interface segregation principle) : il vaut mieux plusieurs interfaces spécifiques à une classe qu’une grosse interface générale, il ne faut pas forcer à implémenter des choses que l’on ne veut pas.

  • Inversion des dépendances (Dependency Inversion Principle) : il ne faut pas dépendre d’une classe concrète mais d’une interface ou classe abstraite s’il est appliqué alors on appliquer également le Open / Closed Principle.

À noter : ce sont avant tout des principes de bon sens. Aucun ne nécessite une connaissance approfondie d'un langage donné. Ces principes, lorsqu'ils sont compris et suivis, permettent d'améliorer la cohésion, de diminuer le couplage, et de favoriser l'encapsulation d'un programme orienté objet.

Étude de cas

Description initiale : Le modèle de voiture

On distingue deux formes de consommation pour un même modèle de voiture :

  • La consommation moyenne sur route, urbaine ou mixte exemples en litres aux 100km.
  • La consommation en fonction de la vitesse qui suit la règle suivante 1), elle ne prétend pas être vraie

  • C(v) = 2(d/v) + K v^2 avec v en km/heures, d en km et k un coefficient variable en fonction des voitures.

    Si une voiture roule à 150 km/h sa consommation est donc en litres aux 100km de :
    C(150) = 2(100/150) + K * 150^2 pour la Cl
    Avec un coefficient de
    3,85*10^-4, C(150) = 9.99583333333; C(64) = 4,7 litres au 100km

    Ce qui en java peut se coder par une ligne comme :
    double consommation = 2*(100/vitesse) + coeff*vitesse*vitesse;
    Quelle modélisation proposez-vous pour supporter les opérations suivantes :
    1. Je veux pouvoir déclarer que le modèle Clio a une consommation moyenne sur Route de 6,13, urbaine de 8,1, etc…
    2. Je veux pouvoir déclarer que le modèle Clio a un coefficient “k” de 3,85*10^-4
    3. Je veux pouvoir demander la consommation moyenne du modèle Clio sur Route (rep. 6,3) ? urbaine (rep. 8,1) ?
    4. Je veux pouvoir demander la consommation moyenne d'une Clio à 150km/h ? (mettons que le coefficient est de 3,85*10^-4 et donc obtenir une réponse de quasi 10).

    Réfléchissez à l'implémentation et proposer un modèle de classe qui répond à ces fonctionnalités, puis implémentez le. On intègre à présent la consommation “Mixte”, qu'est-ce qui change dans votre modèle ? dans votre code?

Modèle final


La classe abstraite ModeleVoitureAbstrait nous permet d’être indépendant de chaque spécialisation de cette classe, rendant ainsi les codes plus simples et cohérent. Nous avons choisi d’implémenter les attributs concernant la consommation directement dans la classe ModeleVoitureAbstrait plutôt que d’en créer une autre, à part, ce qui selon nous rendait les choses inutilement compliquées.

Les classes ModeleVoiture et ModeleVoitureBolide étendent toutes les deux la classe ModeleVoitureAbstrait afin de factoriser le code est d’être utilisable par les méthodes nécessitant un objet de type ModeleVoitureAbstrait.
La classe ModeleVoiturePro est une extension de ModeleVoiture car les deux sont similaires, la seule différence étant que ModeleVoiturePro possède une charge et un calculateur spécifique, que nous associons dans le constructeur de ce dernier. Le faire hériter de la classe abstraite ModeleVoitureAbstrait nous aurait quand même demandé de lui associer le calculateur spécifique.
Ainsi, il était plus aisé de lui faire étendre directement la classe ModeleVoiture.

La classe CalculateurCO2 implémente une interface. Elle est étendue par deux autres classes : CalculateurCO2ADBLUE qui ne possède pas d’argument dans son constructeur et altère la méthode consommation et la classe CalculateurCO2Pro qui modifie juste la méthode consommation et renvoie toujours la même valeur, pour simuler un outil externe calculant la consommation d’un modèle de voiture.

À noter : notre modèle ne respecte pas vraiment le Interface Segregation Principe, en effet, il n’y a pas une interface ou classe abstraite pour chaque type de modèle de voiture par exemple. Cependant on dépend bien à chaque fois d’une interface ou classe abstraite et les classes ont des responsabilités limitées.

À propos

Ce TD permet de mettre en pratique les principes SOLID et ainsi de mieux les retenir par rapport au cours donné en parallèle. Il a été l’occasion de consolider notre maitrise du logiciel Visual Paradigm dans la réalisation de diagramme de classe, ainsi que l’utilisation des tests unitaires JUnit.