Les principes SOLID

SOLID est l’acronyme de 5 principes de conception importants lors de la programmation orientée objet (POO).

S.O.L.I.D signifie:

  • S – Single-responsiblity principle
  • O – Open-closed principle
  • L – Liskov substitution principle
  • I – Interface segregation principle
  • D – Dependency Inversion Principle

 
 
L’intention de ces principes est de rendre les conceptions de logiciels plus compréhensibles, plus faciles à entretenir et à évoluer.

Des exemples seront donnés en PHP, mais s’appliquent à tout langage POO.

Maintenant, regardons chaque principe un par un:
 

Single-responsiblity principle

Une classe ne devrait avoir qu’une seule responsabilité.

Une classe ne devrait être responsable que d’une seule tâche. Si une classe a plus d’une responsabilité, elle devient couplée. Le changement d’une responsabilité entraîne la modification de l’autre responsabilité.
 
Exemple :

class Car {
    public function __construct(String name){ }
    public function getCarName() { }
    public function saveCar(Car c) { }
}

La classe Car viole le SRP.
 
Comment cela viole-t-il SRP?
SRP déclare que les classes devraient avoir une responsabilité, ici, nous pouvons dégager deux responsabilités: la gestion de la base de données et la gestion des propriétés. Le constructeur et « getCarName » gèrent les propriétés tandis que « saveCar » gère le stockage des voitures sur une base de données.
 
Comment cette conception causera-t-elle des problèmes à l’avenir?
Si l’application change d’une manière qui affecte les fonctions de gestion de base de données. Les classes qui utilisent les propriétés de la classe Car devront être recompilées pour compenser les nouveaux changements.

Pour rendre cela conforme à SRP, nous créons une autre classe qui gérera la seule responsabilité de stocker une voiture dans la base de données:

class Car {
    public function __construct(String name){ }
    public function getCarName() { }
}

class CarDB {
    public function saveCar(Car c) { }
    public function getCar(Car c) { }
}

 
 

Open-closed principle

Les entités logicielles (classes, fonctions, etc) doivent être ouvertes pour l’extension et non pour la modification.

Retournons à notre classe Car:

class Car {
    public function __construct(String name){ }
    public function getCarName() { }
}

Nous voulons parcourir la liste des voitures et ajoutons pour chaque voiture le modèle approprié.

public function carModel(Array $cars) {
    foreach($cars as $car) {
        if($car->name == 'Bmw')
            echo('X25');
        if($car->name == 'Audi')
            echo('A7');
    }
}

Array $cars = new array(new Car('Bmw'), new Car('Audi'));
carModel($cars);

La fonction carModel n’est pas conforme au principe open-closed car elle ne peut pas être fermée contre de nouveaux types de voiture.

Si nous ajoutons un nouvel type, Mercedes:

Array $cars = new array(
	new Car('Bmw'), 
	new Car('Audi'),
	new Car('Mercedes')
);

Nous devons modifier la fonction carModel:

public function carModel(Array $cars) {
    foreach($cars as $car) {
        if($car->name == 'Bmw')
            echo('X25');
        if($car->name == 'Audi')
            echo('A7');
        if($car->name == 'Mercedes')
            echo('Classe C');
    }
}

Vous constatez, pour chaque nouvelle voiture, une nouvelle logique est ajoutée à la fonction carModel. Ceci est un exemple assez simple. Lorsque votre application grandit et devient complexe, vous verrez que l’instruction if sera répétée encore et encore dans la fonction carModel chaque fois qu’une nouvelle voiture est ajouté.
 
Maintenant, comment rendre la fonction carModel conforme au principe OCP?

class Car {
	getModel();
}
class Bmw extends Car {
    getModel() {
        return 'X25';
    }
}
class Audi extends Car {
    getModel() {
        return 'A7';
    }
}
class Mercedes extends Car {
    getModel() {
        return 'Classe C';
    }
}

public function carModel(Array $cars) {
    foreach($cars as $car) {
        echo($car->getModel());
    }
}

Array $cars = new array(
	new Car('Bmw'), 
	new Car('Audi'),
	new Car('Mercedes')
);

carModel($cars);

Maintenant, si nous ajoutons une nouvelle voiture, la fonction carModel n’a pas besoin de changer. Tout ce que nous devons faire est d’ajouter une nouvelle classe étendant la classe Car.

La fonction carModel est désormais conforme au principe OCP.
 
 

Liskov substitution principle

Une classe fille doit être substituable à sa classe mère.

Le principe Liskov spécifie que si la classe A est un sous-type de classe B, alors nous devrions pouvoir remplacer B par A sans perturber le comportement de notre programme.

public function carEngine(Array $cars) {
    foreach($cars as $car) {
        if($car->name == 'Bmw')
            echo(bmwEngine($car));
        if($car->name == 'Audi')
            echo(audiEngine($car));
        if($car->name == 'Mercedes')
            echo(mercedesEngine($car));
    }
}

Array $cars = new array(
	new Car('Bmw'), 
	new Car('Audi'),
	new Car('Mercedes')
);
carEngine($cars);

Cela viole le principe LSP et également le principe OCP. Il doit connaître tous les types de voitures et appeler la fonction Engine associée.

Pour que cette fonction suive le principe LSP, nous suivrons les règles suivantes:

  • Si la classe mère (Car) a une méthode qui accepte un paramètre de type classe mère (Car). Sa classe fille (Bmw) doit accepter comme argument un type de classe mère (type Car) ou un type de classe fille (type Bmw).
  • Si la classe mère renvoie un type de classe mère (Car). Sa classe fille doit renvoyer un type de classe mère (type Car) ou un type de classe fille (Bmw).

 
Maintenant, nous pouvons ré-implémenter la fonction carEngine:

public function carEngine(Array $cars) {
    foreach($cars as $car) {
        echo($car->getEngine());
    }
}

Array $cars = new array(
	new Car('Bmw'), 
	new Car('Audi'),
	new Car('Mercedes')
);
carEngine($cars);

La fonction carEngine se soucie moins du type de voiture passé, elle appelle simplement la méthode getEngine. Tout ce qu’il sait, c’est que le paramètre doit être de type Voiture, soit la classe mère soit sa classe fille.

La classe Car et ses classes filles doivent maintenant définir la méthode getEngine:

class Car {
	getEngine();
}
class Bmw extends Car {
    getEngine() {
        //..
    }
}
//....

Comme cous constatez, carEngine n’a pas besoin de connaître le type de voiture pour renvoyer son Engine, il appelle simplement la méthode getEngine du type Car parce que la classe fille doit implémenter la fonction getEngine.
 
 

Interface segregation principle

Un client ne devrait jamais être forcé d’implémenter une interface ou une méthode qu’il n’utilise pas.

Ce principe traite les inconvénients de l’implémentation des grandes interfaces. Regardons l’interface Animal ci-dessous:

interface Animal {
    sayMoo();
    sayMeow();
    //...
}

Cette interface spécifie le son émis par des animaux. La classe Cat, Cow, etc implémentant l’interface Animal doit définir les méthodes sayMoo(), sayMeow(), etc.

interface Animal {
    sayMoo();
    sayMeow();
}

class Cat implements Animal {
    sayMoo(){
        echo 'Moo...';
    }
    sayMeow(){
        echo 'Meow...';
    }   
}

class Cow implements Animal {
    sayMoo(){
        echo 'Moo...';
    }
    sayMeow(){
        echo 'Meow...';
    }   
}

C’est assez drôle de regarder le code ci-dessus. La classe Cat implémente la méthode sayMoo dont elle n’a pas besoin, pareil pour la classe Cow qui implémente la méthode sayMeow.

Pour rendre notre interface Animal conforme au principe ISP, nous séparons les actions sur différentes interfaces:

interface ICat {
    sayMeow();
}

interface ICow {
    sayMoo();
}


class Cat implements ICat {
    sayMeow(){
        echo 'Meow...';
    }   
}

class Cow implements ICow {
    sayMoo(){
        echo 'Moo...';
    } 
}

 

Interface segregation principle

Le module de haut niveau ne doit pas dépendre du module de bas niveau, mais ils doivent dépendre d’abstractions.

Il arrive un moment dans le développement de logiciels où notre application sera largement composée de modules. Lorsque cela se produit, nous devons clarifier les choses en utilisant l’injection de dépendance.
Injection de dépendances PHPDesign Patterns: Injection de dépendances en PHPL’injection de dépendances est un design pattern qui permet d’éviter les dépendances pour un morceau de code ou un logiciel.   Les dépendances peuvent être…Lire plus

Une réflexion sur “Les principes SOLID

  • juin 16, 2021 à 4:20
    Permalien

    Bonjour,

    Merci pour cet article, dans la partie sur le principe Open-closed, ligne de code 26 à 30 :

    Array $cars = new array(
    new Car(‘Bmw’),
    new Car(‘Audi’),
    new Car(‘Mercedes’)
    );

    le correctif ne devrait-il pas être :

    Array $cars = new array(
    new BMW(),
    new Audi(),
    new Mercedes()
    );

    Cordialement

    Répondre

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *