Niveau 34 : Principes SOLID et patterns de conception en PHP

Introduction:

Bienvenue au niveau 34 de notre série de tutoriels sur le développement PHP. Dans ce tutoriel, nous allons explorer les principes SOLID et les patterns de conception en PHP. Ces concepts sont essentiels pour améliorer votre code et faciliter la maintenance de vos applications. Nous allons expliquer chaque principe SOLID avec des exemples concrets de code non conforme et leur refactorisation. Ensuite, nous passerons en revue les patterns de conception regroupés par catégories : créationnels, structurels et comportementaux. Pour chaque pattern, nous expliquerons son objectif, sa structure, ses avantages et inconvénients, et nous montrerons une implémentation PHP avec un cas d'utilisation réaliste. Enfin, nous aborderons l'identification des situations où ces patterns sont appropriés et comment les combiner efficacement.

Les principes SOLID

1. Single Responsibility Principle (SRP)

Le Single Responsibility Principle (SRP) stipule qu'une classe doit avoir une seule raison de changer. Cela signifie qu'une classe ne doit avoir qu'une seule responsabilité et ne doit pas être surchargée avec des fonctionnalités qui ne lui sont pas liées. Voyons un exemple :

 // Mauvaise conception  class User {     public function save() {         // Code pour enregistrer un utilisateur     }      public function sendEmail() {         // Code pour envoyer un e-mail à l'utilisateur     } } 

Dans cet exemple, la classe User est responsable de deux choses : la sauvegarde de l'utilisateur et l'envoi d'e-mails. Pour respecter le SRP, nous pouvons diviser cette classe en deux :

 class User {     public function save() {         // Code pour enregistrer un utilisateur     } }  class EmailSender {     public function sendEmail() {         // Code pour envoyer un e-mail à l'utilisateur     } } 

Maintenant, chaque classe a une seule responsabilité, ce qui facilite la maintenance et la compréhension du code.

2. Open-Closed Principle (OCP)

L'Open-Closed Principle (OCP) stipule que les entités logicielles (classes, modules, fonctions, etc.) doivent être ouvertes pour l'extension mais fermées pour la modification. Cela signifie que vous devez pouvoir ajouter de nouvelles fonctionnalités sans modifier le code existant. Voici un exemple :

 // Mauvaise conception  class Order {     public function calculateTotal() {         // Code pour calculer le total de la commande     }      public function applyDiscount() {         // Code pour appliquer une réduction à la commande     } } 

Dans cet exemple, la classe Order est responsable à la fois du calcul du total de la commande et de l'application de réductions. Pour respecter l'OCP, nous pouvons utiliser le principe de substitution de Liskov (que nous aborderons plus tard) pour séparer les responsabilités :

 abstract class Order {     abstract public function calculateTotal(); }  class StandardOrder extends Order {     public function calculateTotal() {         // Code pour calculer le total de la commande     } }  class DiscountedOrder extends Order {     public function calculateTotal() {         // Code pour calculer le total de la commande avec une réduction     } } 

Maintenant, nous pouvons ajouter de nouveaux types de commande sans modifier la classe Order existante.

3. Liskov Substitution Principle (LSP)

Le Liskov Substitution Principle (LSP) stipule que les objets d'une classe dérivée doivent pouvoir être substitués à des objets de la classe de base, sans altérer le comportement attendu du programme. Voyons un exemple :

 // Mauvaise conception  class Rectangle {     protected $width;     protected $height;      public function setWidth($width) {         $this->width = $width;     }      public function setHeight($height) {         $this->height = $height;     }      public function getArea() {         return $this->width * $this->height;     } }  class Square extends Rectangle {     public function setWidth($width) {         $this->width = $width;         $this->height = $width;     }      public function setHeight($height) {         $this->width = $height;         $this->height = $height;     } } 

Dans cet exemple, nous avons une classe Rectangle avec une largeur et une hauteur, et une classe Square qui hérite de Rectangle. Cependant, lorsque nous utilisons la classe Square, nous ne pouvons pas garantir que la largeur et la hauteur seront indépendantes. Pour respecter le LSP, nous devons revoir notre conception :

 interface Shape {     public function getArea(); }  class Rectangle implements Shape {     protected $width;     protected $height;      public function setWidth($width) {         $this->width = $width;     }      public function setHeight($height) {         $this->height = $height;     }      public function getArea() {         return $this->width * $this->height;     } }  class Square implements Shape {     protected $side;      public function setSide($side) {         $this->side = $side;     }      public function getArea() {         return $this->side * $this->side;     } } 

Maintenant, nous avons une interface Shape qui garantit que toutes les formes ont une méthode getArea(). La classe Square n'est plus une sous-classe de Rectangle, mais elle implémente l'interface Shape et a son propre comportement.

4. Interface Segregation Principle (ISP)

L'Interface Segregation Principle (ISP) stipule qu'il vaut mieux avoir de nombreuses interfaces spécifiques plutôt qu'une seule interface générale. Cela permet de minimiser les dépendances et d'éviter que les classes n'implémentent des méthodes inutiles pour elles. Voici un exemple :

 interface Animal {     public function walk();     public function swim();     public function fly(); }  class Dog implements Animal {     public function walk() {         // Code pour faire marcher le chien     }      public function swim() {         // Code pour faire nager le chien     }      public function fly() {         // Code inutile pour le chien     } } 

Dans cet exemple, l'interface Animal oblige la classe Dog à implémenter la méthode fly(), qui n'est pas pertinente pour un chien. Pour respecter l'ISP, nous pouvons diviser l'interface Animal en plusieurs interfaces :

 interface Walkable {     public function walk(); }  interface Swimmable {     public function swim(); }  interface Flyable {     public function fly(); }  class Dog implements Walkable, Swimmable {     public function walk() {         // Code pour faire marcher le chien     }      public function swim() {         // Code pour faire nager le chien     } } 

Maintenant, la classe Dog n'a pas besoin d'implémenter la méthode fly(), ce qui réduit les dépendances inutiles.

5. Dependency Inversion Principle (DIP)

Le Dependency Inversion Principle (DIP) stipule que les modules de haut niveau ne doivent pas dépendre des modules de bas niveau, ils doivent plutôt dépendre d'abstractions. Cela permet de réduire les dépendances et de faciliter les tests unitaires. Voyons un exemple :

 class UserRepository {     public function save(User $user) {         // Code pour enregistrer un utilisateur     } }  class UserService {     private $userRepository;      public function __construct(UserRepository $userRepository) {         $this->userRepository = $userRepository;     }      public function createUser($data) {         // Code pour créer un utilisateur         $user = new User($data);         $this->userRepository->save($user);     } } 

Dans cet exemple, la classe UserService dépend directement de la classe UserRepository. Pour respecter le DIP, nous devons introduire une abstraction :

 interface UserRepositoryInterface {     public function save(User $user); }  class UserRepository implements UserRepositoryInterface {     public function save(User $user) {         // Code pour enregistrer un utilisateur     } }  class UserService {     private $userRepository;      public function __construct(UserRepositoryInterface $userRepository) {         $this->userRepository = $userRepository;     }      public function createUser($data) {         // Code pour créer un utilisateur         $user = new User($data);         $this->userRepository->save($user);     } } 

Maintenant, la classe UserService dépend de l'interface UserRepositoryInterface, ce qui facilite les tests unitaires et permet de remplacer facilement l'implémentation de UserRepository.

Les patterns de conception

Créationnels

1. Factory

Le pattern Factory permet de créer des objets sans spécifier explicitement la classe de l'objet à créer. Il est utile lorsque vous avez besoin de créer des objets de différentes sous-classes, mais que vous ne voulez pas coupler votre code à des classes spécifiques. Voici un exemple :

 interface Product {     public function getName(); }  class Car implements Product {     public function getName() {         return 'Car';     } }  class Bike implements Product {     public function getName() {         return 'Bike';     } }  class ProductFactory {     public function create($type) {         if ($type === 'car') {             return new Car();         }          if ($type === 'bike') {             return new Bike();         }          throw new InvalidArgumentException('Invalid product type');     } } 

Dans cet exemple, la classe ProductFactory crée des objets de différentes sous-classes de Product en fonction du type spécifié. Cela permet de créer des objets sans connaître leur classe exacte.

2. Singleton

Le pattern Singleton permet de s'assurer qu'une classe n'a qu'une seule instance et fournit un point d'accès global à cette instance. Il est utile lorsque vous avez besoin d'une classe unique pour coordonner les actions à travers l'application. Voici un exemple :

 class DatabaseConnection {     private static $instance;      private function __construct() {         // Code pour initialiser la connexion à la base de données     }      public static function getInstance() {         if (self::$instance === null) {             self::$instance = new self();         }          return self::$instance;     } } 

Dans cet exemple, la classe DatabaseConnection a une méthode getInstance() qui renvoie toujours la même instance de la classe. Cela garantit qu'il n'y a qu'une seule connexion à la base de données dans l'application.

3. Builder

Le pattern Builder permet de construire des objets complexes étape par étape. Il est utile lorsque vous avez des objets avec de nombreux paramètres optionnels et que vous voulez rendre leur création plus lisible. Voici un exemple :

 class User {     private $name;     private $email;     private $age;     // ... autres propriétés      private function __construct() {}      public static function builder() {         return new self();     }      public function withName($name) {         $this->name = $name;         return $this;     }      public function withEmail($email) {         $this->email = $email;         return $this;     }      public function withAge($age) {         $this->age = $age;         return $this;     }      // ... autres méthodes      public function build() {         // Valider les propriétés         // Construire et retourner l'objet User     } } 

Dans cet exemple, la classe User utilise un concept de builder pour construire des objets User. Les méthodes withX() permettent de spécifier les différentes propriétés de l'objet, et la méthode build() crée et retourne l'objet final.

Structurels

1. Adapter

Le pattern Adapter permet de faire collaborer des classes incompatibles en les enveloppant dans une classe adaptateur qui expose une interface commune. Il est utile lorsque vous avez des classes existantes avec des interfaces différentes et que vous voulez les utiliser ensemble. Voici un exemple :

 interface BookInterface {     public function open();     public function turnPage(); }  class Book implements BookInterface {     public function open() {         // Code pour ouvrir le livre     }      public function turnPage() {         // Code pour tourner la page du livre     } }  class Kindle {     public function turnOn() {         // Code pour allumer le Kindle     }      public function pressNextButton() {         // Code pour passer à la page suivante sur le Kindle     } }  class KindleAdapter implements BookInterface {     private $kindle;      public function __construct(Kindle $kindle) {         $this->kindle = $kindle;     }      public function open() {         $this->kindle->turnOn();     }      public function turnPage() {         $this->kindle->pressNextButton();     } } 

Dans cet exemple, la classe Book et la classe Kindle ont des interfaces différentes, mais nous voulons les utiliser ensemble. La classe KindleAdapter enveloppe la classe Kindle et expose une interface commune (BookInterface) pour être utilisée avec la classe Book.

2. Decorator

Le pattern Decorator permet d'ajouter de nouvelles fonctionnalités à des objets existants de manière transparente, sans modifier leur structure de base. Il est utile lorsque vous avez besoin d'ajouter des fonctionnalités à un objet sans modifier son code existant. Voici un exemple :

 interface Pizza {     public function getDescription();     public function getCost(); }  class Margherita implements Pizza {     public function getDescription() {         return 'Margherita Pizza';     }      public function getCost() {         return 5.99;     } }  class PizzaDecorator implements Pizza {     protected $pizza;      public function __construct(Pizza $pizza) {         $this->pizza = $pizza;     }      public function getDescription() {         return $this->pizza->getDescription();     }      public function getCost() {         return $this->pizza->getCost();     } }  class ExtraCheese extends PizzaDecorator {     public function getDescription() {         return parent::getDescription() . ', Extra Cheese';     }      public function getCost() {         return parent::getCost() + 1.99;     } }  $pizza = new Margherita(); $pizzaWithExtraCheese = new ExtraCheese($pizza);  echo $pizzaWithExtraCheese->getDescription(); // Output: Margherita Pizza, Extra Cheese echo $pizzaWithExtraCheese->getCost(); // Output: 7
Alex M. just bought Module SEO Pro
New! Script PHP Ultra Performance available
-30% on all Gaming modules this weekend!
12 developers are viewing this product now
FLASH SALE ENDS IN:
23 H
:
59 M
:
59 S
HOME
BLOG
0
CART
PROFILE