Contact salesFree trial
Blog

Compréhension des principes SOLID dans la conception de logiciels

PHPDevOpsdette technique
Partager

Les principes SOLID facilitent la maintenance et l'adaptation de votre code. Ces cinq pratiques de conception orientée objet vous aident à écrire un code plus propre et plus fiable.

Nous allons explorer chaque principe à l'aide d'exemples pratiques permettant de créer des applications faciles à maintenir, qu'il s'agisse de bases de code uniques ou de systèmes distribués.

Que sont les principes SOLID dans le développement de logiciels ?

L'écriture d'un code maintenable est un défi. Les principes SOLID vous donnent des lignes directrices pratiques pour écrire un meilleur code. Robert C. Martin a créé ces principes pour résoudre des problèmes courants tels que la rigidité du code, les bogues en cascade et les systèmes complexes.

Chaque principe SOLID cible un défi de codage spécifique :

S : Principe de responsabilité unique (SRP) : simplifier la maintenance du code

Le principe de responsabilité unique permet à votre code d'être maintenable. Chaque classe effectue une tâche spécifique, ni plus ni moins.

Pensez-y de la manière suivante : L'authentification de l'utilisateur doit être séparée de la journalisation ou des notifications. Lorsqu'une classe essaie de faire plusieurs choses, les tests et les mises à jour deviennent plus complexes.

Relations entre les classes SRP

Le diagramme utilise la composition pour montrer comment UserRegistration fonctionne avec d'autres classes. Chaque flèche de diamant creux signifie que UserRegistration fait référence à une classe mais ne contrôle pas son cycle de vie.

Légende du diagramme :

  • signifie méthodes/propriétés publiques
  • signifie méthodes/propriétés privées
  • Les flèches indiquent les dépendances entre les classes
  • Les encadrés indiquent les noms des classes et leurs membres

Voici comment la séparation des responsabilités en matière de code permet de créer des applications plus propres et plus faciles à maintenir :

// Cet exemple illustre le principe de responsabilité unique (SRP) // Chaque classe a une tâche spécifique, ce qui rend le code plus facile à maintenir et à tester // UserDataFormatter : Responsable de la conversion des données utilisateur dans un format cohérent class UserDataFormatter { public function formatUserData($user) { // Extrait et formate les informations utilisateur essentielles return [ 'name' => $user->getFullName(), // Obtient le nom complet de l'utilisateur 'email' => $user->getEmail(), // Obtient l'email de l'utilisateur 'joined' => $user->getJoinDate()->format('Y-m-d') // Formate la date d'adhésion ] ; } } } // UserDataValidator : Responsable de la conformité des données aux exigences class UserDataValidator { public function validateUserData($userData) { // Effectue deux contrôles de validation : // 1. vérifie que le format de l'adresse électronique est valide // 2. s'assure que le champ du nom n'est pas vide return filter_var($userData['email'], FILTER_VALIDATE_EMAIL) && !empty($userData['name']) ; } } // UserDataPersistence : Responsable des opérations de base de données class UserDataPersistence { private $database ; // Initialise la connexion à la base de données public function __construct($database) { $this->database = $database ; } // Gère l'enregistrement des données utilisateur dans la base de données public function saveUser($userData) { // Effectue l'opération d'insertion dans la base de données return $this->database->insert('users', $userData) ; } } } // UserRegistration : Orchestre le processus d'enregistrement // Agit comme une façade, coordonnant d'autres classes spécialisées class UserRegistration { private $formatter ; private $validator ; private $persistence ; // Injection de dépendances dans le constructeur public function __construct( UserDataFormatter $formatter, UserDataValidator $validator, UserDataPersistence $persistence ) { $this->formatter = $formatter ; $this->validator = $validator ; $this->persistence = $persistence ; } // Méthode d'enregistrement principale qui coordonne l'ensemble du processus public function registerUser($user) { // Étape 1 : Formatez les données de l'utilisateur $userData = $this->formatter->formatUserData($user) ; // Étape 2 : Validez les données formatées if ($this->validator->validateUserData($userData)) { // Étape 3 : Si la validation est réussie, sauvegarder dans la base de données return $this->persistence->saveUser($userData) ; } // Si la validation échoue, lancer une exception throw new ValidationException('Invalid user data') ; } } // EXEMPLE DE DEMONSTRATION D'UTILISATION // On montre ci-dessous comment tous les composants fonctionnent ensemble // Créer une base de données fictive pour la démonstration $database = new class { public function insert($table, $data) { // Simule l'insertion dans la base de données et renvoie le succès echo "Inserted into $table : " . json_encode($data) . "\N" ; return true ; } } ; try { // Étape 1 : Initialisation de tous les composants requis $formatter = new UserDataFormatter() ; $validator = new UserDataValidator() ; $persistence = new UserDataPersistence($database) ; // Étape 2 : Création du service principal UserRegistration $userRegistration = new UserRegistration($formatter, $validator, $persistence) ; // Étape 3 : Créer un objet utilisateur fictif pour les tests $user = new class { public function getFullName() { return "Jane Doe" ; } public function getEmail() { return "jane@example.com" ; } public function getJoinDate() { return new DateTime() ; } } ; // Étape 4 : Tenter d'enregistrer l'utilisateur $userRegistration->registerUser($user) ; echo "User successfully registered !" ; } catch (ValidationException $e) { // Gérer les erreurs de validation qui surviennent echo "Failed to register user : " . $e->getMessage() ; } // Classe d'exception personnalisée pour les erreurs spécifiques à la validation class ValidationException extends Exception {}


Chaque classe a une tâche unique et ciblée. Le formateur formate les données. Le validateur vérifie les données. La couche de persistance enregistre les données. La classe d'enregistrement coordonne proprement ces éléments. Cette structure facilite les tests et la maintenance.

Cette conception propre s'aligne sur le principe "ouvert-fermé". Vous souhaitez ajouter de nouvelles règles de formatage ou de nouveaux contrôles de validation ? Créez de nouvelles classes qui suivent le modèle existant. Votre code reste intact tandis que les fonctionnalités se développent.

O : Principe ouvert-fermé (OCP) : écrire un code extensible et évolutif

Le principe ouvert-fermé (OCP) vous permet d'ajouter des fonctionnalités sans modifier le code existant. Votre base de code reste stable tout en se développant. Au lieu de modifier le code existant, étendez les fonctionnalités grâce à de nouvelles classes et interfaces.

L : Principe de substitution de Liskov (PSL) : éviter les ruptures de code dans la POO

Le principe de substitution de Liskov (LSP) signifie que les classes enfants doivent fonctionner partout où leurs classes parents fonctionnent. Si ce n'est pas le cas, votre code se brisera de manière inattendue.

Voici une violation courante du principe de substitution de Liskov et la façon de la corriger :

// ❌ Violation du PSL : Ceci viole le principe de substitution de Liskov car // une sous-classe (Pingouin) modifie le comportement attendu de sa classe mère (Oiseau) class Bird { public function fly() { // Logique de vol } } class Penguin extends Bird { public function fly() { // Ceci rompt le contrat établi par la classe mère throw new Exception("Penguins can't fly !") ; } } // ✅ Refactorisé : Meilleure conception utilisant la composition plutôt que l'héritage // Cette approche sépare la capacité de vol de la classification des oiseaux interface Flyable { public function fly() ; } // Seuls les oiseaux qui peuvent réellement voler implémentent l'interface Flyable class Sparrow implémente Flyable { public function fly() { // Les oiseaux volants implémentent leur comportement de vol spécifique } } } // Le pingouin n'implémente pas Flyable, évitant le problème de l'héritage forcé class Penguin { // Les pingouins ont leurs propres comportements sans être forcés d'implémenter fly() }.

I : Principe de séparation des interfaces (ISP) : concevoir des interfaces propres et ciblées

Le principe de ségrégation des interfaces vous aide à écrire un code plus propre en gardant les interfaces petites et ciblées. De cette manière, les classes n'implémentent que les méthodes qu'elles utilisent réellement.

Voyons comment diviser les interfaces surchargées en interfaces plus petites et plus ciblées.

// Interface Animal { public function fly() ; // Problème : Les poissons ne devraient pas en avoir besoin public function swim() ; // Problème : Les oiseaux ne devraient pas en avoir besoin } class Fish implements Animal { public function swim() { // Logique de nage } public function fly() { // Forcé d'implémenter une méthode non pertinente throw new Exception("Fish cannot fly") ; } } } ✅ Refactorisé : Suivre l'ISP avec des interfaces ciblées et spécifiques // Chaque classe n'implémente que les méthodes dont elle a réellement besoin interface Flyable { public function fly() ; // Interface pour les créatures volantes } interface Swimmable { public function swim() ; // Interface pour les créatures nageuses } class Fish implements Swimmable { public function swim() { // Le poisson n'implémente que ce qu'il peut réellement faire // Cela suit l'ISP en n'imposant pas de méthodes inutiles } } class Bird implements Flyable { public function fly() { // Les oiseaux n'implémentent que le comportement de vol // Aucune méthode inutilisée n'est imposée à la classe } }.

D : Principe d'inversion de dépendance (DIP) : améliorer la flexibilité du code grâce aux abstractions

Le principe d'inversion de la dépendance vous aide à écrire un meilleur code en utilisant des interfaces au lieu d'implémentations directes. Vos modules de haut niveau travaillent avec des abstractions et non des spécificités. Cela vous permet d'échanger des composants sans casser votre système.

Voici un exemple pratique :

// ❌ Violates DIP : High-level module depends on low-level module class EmailNotifier { public function send($message) { // Logic for sending email } } class Notification { private $emailNotifier ; public function __construct() { $this->emailNotifier = new EmailNotifier() ; } public function notify($message) { $this->emailNotifier->send($message) ; } } // ✅ Refactored : Dépend des abstractions, pas des implémentations concrètes interface Notifier { public function send($message) ; } class EmailNotifier implements Notifier { public function send($message) { // Logique d'envoi d'un email } } class SMSNotifier implements Notifier { public function send($message) { // Logique d'envoi d'un SMS } } class Notification { private $notifier ;
    
    public function __construct(Notifier $notifier) { $this->notifier = $notifier ; } public function notify($message) { $this->notifier->send($message) ; } } // Cette conception permet d'échanger EmailNotifier avec SMSNotifier sans modifier la notification.

Les principes SOLID sont des outils simples qui fonctionnent ensemble. Lorsqu'ils sont combinés correctement, ils vous aident à écrire un code propre, plus facile à maintenir et à déboguer.

Voici une décomposition claire de ce que fait chaque principe :

PrincipeRésuméAvantage principal
Responsabilité unique (SRP)Une classe ne doit avoir qu'une seule raison de changerRéduit la complexité de la maintenance
Ouvert-fermé (OCP)Les entités logicielles doivent être ouvertes à l'extension mais fermées à la modification.Permet l'ajout de fonctionnalités évolutives
Substitution de Liskov (LSP)Les classes dérivées peuvent être substituées à leurs classes de baseAssure la cohérence du comportement
Séparation des interfaces (ISP)Les clients ne doivent pas être contraints de dépendre d'interfaces qu'ils n'utilisent pas.Minimise le couplage entre les composants
Inversion de dépendance (DIP)Dépendre d'abstractions et non d'implémentations concrètesAméliore la flexibilité du système


La ségrégation des interfaces et l'inversion de dépendance permettent de construire des systèmes adaptables. La ségrégation des interfaces permet à chaque microservice de n'exposer que les fonctions essentielles. L'inversion de dépendance permet d'échanger des composants sans modifier le code de base.

L'inversion de dépendance fonctionne particulièrement bien pour les systèmes de plugins. L'utilisation d'interfaces au lieu de dépendances codées en dur rend les composants interchangeables, ce qui facilite l'intégration de nouveaux outils.

Par exemple, un CMS peut basculer entre SQL, NoSQL ou des fichiers pour le stockage. La ségrégation des interfaces permet de maintenir les connexions propres et minimales. Ensemble, ces principes créent des systèmes qui s'adaptent aux nouvelles exigences.

Comment les principes SOLID rendent la conception de logiciels maintenable et évolutive

L'écriture d'un code maintenable est un défi. Les corrections rapides effectuées pendant les délais créent une dette technique qu'il est difficile de réparer par la suite. Les principes SOLID offrent une solution.

Ces principes aident les équipes à élaborer un code stable et flexible, plus facile à comprendre et à modifier. Résultat : moins de bogues et des cycles de développement plus rapides.

Une structure de code claire permet un meilleur travail d'équipe. Lorsque chaque composant a un objectif spécifique, les tests et le débogage deviennent plus simples.

La norme SOLID favorise la croissance. Grâce au principe d'ouverture et de fermeture, vous pouvez étendre les fonctionnalités sans modifier le code existant, ce qui est essentiel pour les projets évolutifs.

Au fur et à mesure que les besoins évoluent, les principes SOLID garantissent que votre code peut s'adapter. Ajoutez des caractéristiques ou modifiez des fonctionnalités sans procéder à un remaniement majeur.

Si les raccourcis peuvent sembler efficaces dans l'immédiat, ils créent des problèmes à long terme : un développement plus lent, davantage de bogues et la frustration des développeurs. Les principes SOLID permettent d'obtenir un code plus propre et plus facile à maintenir.

Robert C. Martin le dit clairement : "Une bonne architecture rend le système facile à comprendre, facile à développer, facile à maintenir et facile à déployer. L'objectif ultime est de minimiser le coût de la durée de vie du système et de maximiser la productivité des programmeurs". Les principes SOLID rendent cela possible.

3. Décomposition des principes SOLID (avec exemples)

Principe de la responsabilité unique (SRP)

Chaque classe ne doit faire qu'une seule chose.

Prenons l'exemple d'une classe de rapport qui formate les données et les écrit dans une base de données. Cela pose des problèmes : lorsque vous mettez à jour le formatage ou que vous changez de base de données, vous risquez de casser les deux fonctionnalités.

La solution ? Diviser la classe en ReportFormatter et ReportWriter. Chaque classe a un objectif précis. Vous souhaitez modifier la mise en forme d'un rapport ? Mettez à jour ReportFormatter. Besoin de changer de base de données ? Modifiez ReportWriter.

Cette séparation simplifie également les tests, puisque vous pouvez vérifier séparément les opérations de formatage et de base de données.

Voici un exemple de code :

// ❌ Violation de l'ASR : une classe gérant plusieurs responsabilités class Report { public function formatData($data) { // Formatage des données en JSON // Cela viole l'ASR en mélangeant les préoccupations de formatage et de stockage } public function writeToDatabase($data) { // Écriture des données dans la base de données // Cela crée un couplage étroit entre le formatage et le stockage des données } } } // ✅ Refactorisé : Suivre SRP avec une séparation claire des préoccupations class ReportFormatter { public function formatData($data) { // Formater les données en JSON // Cette classe a une seule responsabilité : le formatage des données } } } class ReportWriter { public function writeToDatabase($data) { // Écrire les données dans la base de données // Cette classe a une seule responsabilité : la persistance des données // Le découplage permet de faciliter les tests et la maintenance } } }.

  • Principale leçon à retenir : L'ASR simplifie le code en donnant à chaque composant un objectif clair et unique. Cela réduit les coûts de maintenance et facilite les changements.

Erreurs courantes à éviter lors de l'application des principes SOLID

Même les développeurs expérimentés peuvent avoir du mal à appliquer les principes SOLID. Voici les erreurs les plus courantes à éviter :

Surcharge d'interfaces

Ne créez pas trop de petites interfaces. Regroupez les fonctions connexes de manière logique. Par exemple, un système de permissions fonctionne mieux avec une seule interface UserPermissions qu'avec des interfaces séparées pour la lecture, l'écriture et la gestion des rôles.

Mauvaise utilisation des classes d'aide

Évitez de déverser de multiples tâches dans des classes d'aide. Décomposez-les plutôt en composants ciblés. Exemple : Remplacer un DataProcessorHelper par des classes DataValidator, DataFormatter et Logger distinctes.

Abstractions inutiles

Ne créez des interfaces que lorsqu'elles apportent une valeur ajoutée. Une simple classe StringFormatter n'a pas besoin d'interface - elle ajoute de la complexité sans aucun avantage.

Garder l'équilibre

N'abusez pas d'un seul principe. Trop de séparation crée une complexité inutile. Une séparation insuffisante rend le code rigide. Laissez les principes SOLID guider vos décisions de conception sans les contraindre.

Le respect de ces principes permet de créer un code maintenable et évolutif.

Pourquoi les principes SOLID sont-ils importants pour la conception de logiciels propres et modulaires ?

Il est essentiel d'écrire un code facile à maintenir. Les principes SOLID facilitent la modification et l'évolution de votre code. Voici comment :

Votre code doit s'adapter à la croissance de votre projet. Grâce aux principes d'ouverture et d'inversion de dépendance, vous pouvez ajouter des fonctionnalités sans casser les fonctionnalités existantes.

En voici un exemple : Supposons que vous construisiez un site de commerce électronique qui gère le calcul des taxes. Grâce à ces principes, vous pouvez ajouter de nouvelles règles fiscales pour différents pays sans modifier le code de base de la caisse.

Voyons cela en pratique :

// Exemple démontrant la violation du principe d'ouverture et de fermeture (OCP) // ❌ Mauvaise pratique : Cette implémentation nécessite de modifier le code existant pour ajouter de nouvelles formes class Shape { // Cette méthode utilise une logique conditionnelle qui doit être modifiée pour chaque nouveau type de forme // Violation de l'OCP car la classe doit être modifiée pour étendre la fonctionnalité public function calculateArea($type, $dimensions) { if ($type === 'circle') { return pi() * pow($dimensions['radius'], 2) ;
        } elseif ($type === 'square') { return pow($dimensions['side'], 2) ; } } } // Exemple démontrant une implémentation correcte suivant l'OCP // ✅ Bonne pratique : Utiliser le polymorphisme pour permettre une extension sans modification // La classe de base abstraite définit l'interface que toutes les formes doivent implémenter // Cela crée un contrat que les classes concrètes doivent respecter abstract class Shape { // La méthode abstraite oblige toutes les classes enfants à implémenter leur propre calcul de surface abstract public function calculateArea() ; } // Implémentation concrète pour le cercle // Chaque forme est responsable de son propre calcul de surface class Circle extends Shape { private $radius ; // Le constructeur initialise le cercle avec son rayon public function __construct($radius) { $this->radius = $radius ;
    } // Implémentation spécifique pour le calcul de la surface du cercle // π * r² public function calculateArea() { return pi() * pow($this->radius, 2) ; } } // Implémentation concrète pour le carré // De nouvelles formes peuvent être ajoutées en créant de nouvelles classes sans modifier le code existant class Square extends Shape { private $side ;
    
    // Le constructeur initialise le carré avec la longueur de son côté public function __construct($side) { $this->side = $side ; } // Implémentation spécifique pour le calcul de la surface du carré // côté * côté public function calculateArea() { return pow($this->side, 2) ; } } // Pour ajouter une nouvelle forme, il suffit de créer une nouvelle classe qui étend Shape // Cela suit l'OCP puisque le code existant reste inchangé.
  • Modularité par conception - SOLID crée des composants réutilisables. Un module d'authentification bien structuré peut être utilisé dans plusieurs projets sans modification.
  • L'attention portée à l'équipe - Des limites claires aident les équipes à travailler de manière indépendante. Les nouveaux développeurs commencent à contribuer plus rapidement grâce à des composants ciblés et à usage unique.
  • Architecture prête pour le cloud - Les petits composants ciblés se déploient et s'adaptent facilement aux environnements cloud et aux microservices.

Comment utiliser les principes SOLID pour les microservices et les systèmes prêts pour le cloud ?

Upsun fournit les outils dont vous avez besoin pour écrire un meilleur code, que vous construisiez des monolithes ou des microservices.

Test modulaire

Évoluer sans contrainte

  • Concentrez-vous sur le code pendant que l'infrastructure évolue automatiquement
  • Utiliser des flux de travail Git qui supportent des changements propres et extensibles

Structure du code

  • Profilez et décomposez efficacement le code monolithique

Dépendances

  • Garder les implémentations et les abstractions synchronisées grâce au contrôle de version

Mettre en œuvre les principes de SOLID

Voici comment appliquer efficacement les principes SOLID :

  1. Diviser les grandes classes en composants ciblés : Chaque composant doit effectuer une tâche spécifique. Cela permet d'améliorer les tests et la maintenance.
  2. Apporter des améliorations incrémentales : Modifiez votre code progressivement. Évitez les réécritures complètes.
  3. Testez en permanence : Exécutez des tests après chaque modification afin de détecter rapidement les problèmes.

Le respect de ces pratiques permet d'obtenir un code plus facile à maintenir et à adapter, ce qui est essentiel pour les projets en pleine croissance.

Votre meilleur travail
est à l'horizon

Essai gratuit
Discord
© 2025 Platform.sh. All rights reserved.