Principes SOLID en C++ : de la théorie à la pratique
SOLID est un acronyme désignant cinq principes fondamentaux de la programmation orientée objet, formulés par Robert Martin (Oncle Bob). Dans le contexte du C++, ces principes aident à créer un code flexible, maintenable et testable. Dans cet article, nous allons analyser chaque principe avec des exemples pratiques en C++.
1. Single Responsibility Principle (SRP) — Principe de responsabilité unique
Essence : Chaque classe ne doit avoir qu'une seule raison de changer. En termes simples, la classe s'occupe d'une seule tâche.
Mauvais exemple (violation du SRP) :
class Report {public: void generateReport() { /* génération du rapport */ } void saveToFile(const std::string& filename) { /* sauvegarde dans un fichier */ } void printReport() { /* impression du rapport */ }};Ici, la classe Report est responsable de la génération, de la sauvegarde et de l'impression — trois responsabilités différentes.
Bon exemple (respect du SRP) :
class Report {public: std::string generate() { /* génération du rapport */ return "report data"; }};
class ReportSaver {public: void save(const std::string& data, const std::string& filename) { // sauvegarde dans un fichier }};
class ReportPrinter {public: void print(const std::string& data) { // impression du rapport }};Maintenant, chaque classe a une responsabilité claire et unique, ce qui simplifie les tests et les modifications.
2. Open-Closed Principle (OCP) — Principe d'ouverture/fermeture
Essence : Les classes doivent être ouvertes à l'extension, mais fermées à la modification. Vous devez pouvoir ajouter de nouvelles fonctionnalités sans modifier le code existant.
Mauvais exemple :
enum class ShapeType { Circle, Rectangle };
class Shape {public: ShapeType type;};
class AreaCalculator {public: double calculateArea(const Shape& shape) { if (shape.type == ShapeType::Circle) { // calcul de l'aire du cercle return 3.14 * radius * radius; } else if (shape.type == ShapeType::Rectangle) { // calcul de l'aire du rectangle return width * height; } return 0.0; }};Lors de l'ajout d'une nouvelle forme (par exemple, un triangle), il faudra modifier AreaCalculator.
Bon exemple (via le polymorphisme) :
class Shape {public: virtual double area() const = 0; virtual ~Shape() = default;};
class Circle : public Shape { double radius;public: explicit Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; }};
class Rectangle : public Shape { double width, height;public: Rectangle(double w, double h) : width(w), height(h) {} double area() const override { return width * height; }};
class AreaCalculator {public: double calculateArea(const Shape& shape) { return shape.area(); }};Maintenant, pour ajouter une nouvelle forme, il suffit de créer une nouvelle classe dérivée, sans toucher à AreaCalculator.
3. Liskov Substitution Principle (LSP) — Principe de substitution de Barbara Liskov
Essence : Les objets des sous-classes doivent être interchangeables avec les objets de la classe de base sans compromettre la correction du programme.
Violation classique (rectangle-carré) :
class Rectangle {protected: int width, height;public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } int area() const { return width * height; }};
class Square : public Rectangle {public: void setWidth(int w) override { width = w; height