Principes SOLID en C++ : théorie et pratique de A à Z
SOLID est un acronyme désignant cinq principes fondamentaux de la programmation orientée objet et de la conception, formulés par Robert Martin (également connu sous le nom de "Uncle Bob"). Pour C++, ces principes sont particulièrement importants car ils permettent de créer un code flexible, maintenable et testable, facile à faire évoluer. Dans cet article, nous analyserons chaque principe d'un point de vue théorique et le soutiendrons par des exemples pratiques en C++.
Qu'est-ce que SOLID et pourquoi en a-t-on besoin ?
SOLID est un ensemble de règles qui aident le développeur à éviter le "mauvais" code. Le respect de ces principes conduit à une réduction du couplage et à une augmentation de la cohésion des modules. En C++, où les puissantes capacités des templates et l'héritage multiple peuvent facilement mener au chaos, SOLID sert de boussole fiable. Chaque lettre de l'acronyme se décode comme suit :
- S — Single Responsibility Principle (Principe de responsabilité unique)
- O — Open-Closed Principle (Principe d'ouverture/fermeture)
- L — Liskov Substitution Principle (Principe de substitution de Barbara Liskov)
- I — Interface Segregation Principle (Principe de ségrégation des interfaces)
- D — Dependency Inversion Principle (Principe d'inversion des dépendances)
Principe de responsabilité unique (Single Responsibility)
Théorie : Une classe ne doit avoir qu'une seule raison de changer. Autrement dit, une classe ne doit être responsable que d'une seule partie de la fonctionnalité du programme.
Pratique en C++ : La violation de ce principe conduit souvent à des "objets divins" qui font tout à la fois.
// ❌ Mauvais : la classe viole le SRPclass Report {public: void generateReport() { /* génération des données */ } void saveToFile(const std::string& filename) { /* écriture dans un fichier */ } void printReport() { /* envoi à l'imprimante */ }};
// ✅ Bon : on sépare les responsabilitésclass ReportData {public: std::string generate() { return "Données du rapport"; }};
class ReportSaver {public: void save(const std::string& data, const std::string& filename) { std::ofstream file(filename); file << data; }};
class ReportPrinter {public: void print(const std::string& data) { /* impression */ }};Dans le bon exemple, chaque classe est responsable de sa propre tâche. Cela simplifie les tests et la modification du code.
Principe d'ouverture/fermeture (Open-Closed)
Théorie : Les entités logicielles (classes, modules, fonctions) doivent être ouvertes à l'extension, mais fermées à la modification.
Pratique en C++ : On y parvient grâce à l'abstraction et au polymorphisme.
// ❌ Mauvais : pour ajouter une nouvelle forme, il faut modifier le codeclass AreaCalculator {public: double calculateArea(std::string shapeType, double param) { if (shapeType == "circle") { return 3.14 * param * param; } else if (shapeType == "square") { return param * param; } return 0.0; }};
// ✅ Bon : on utilise le polymorphismeclass Shape {public: virtual double area() const = 0; virtual ~Shape() = default;};
class Circle : public Shape { double radius;public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; }};
class Square : public Shape { double side;public: Square(double s) : side(s) {} double area() const override { return side * side; }};
class AreaCalculatorOCP {public: double calculate(const Shape& shape) { return shape.area(); }};Maintenant, pour ajouter une nouvelle forme (par exemple, un triangle), il n'est pas nécessaire de modifier A