Introduction : SOLID dans le contexte du C++
\nSOLID est l'acronyme de cinq principes fondamentaux de conception orientée objet, formulés par Robert Martin. Pour le C++, en tant que langage doté d'un typage statique puissant, d'héritage multiple et de RAII, ces principes revêtent une importance particulière. Au niveau avancé, la compréhension de SOLID permet de créer des systèmes flexibles, testables et extensibles, en évitant les anti-patrons tels que la « classe divine » ou le couplage rigide.
\nDans cet article, nous analyserons chaque principe à l'aide d'exemples pratiques de code C++ (17/20), en mettant l'accent sur les nuances d'implémentation : utilisation de std::unique_ptr, std::function, CRTP, destructeurs virtuels et idiomes modernes.
1. Single Responsibility Principle (SRP) — Principe de responsabilité unique
\nFormulation : Une classe ne doit avoir qu'une seule raison de changer.
\nEn pratique, cela signifie qu'une classe est responsable d'une seule entité métier ou d'une seule opération. La violation du SRP entraîne un « couplage » des responsabilités, ce qui complique les tests et la modification.
\nExemple de violation
\nclass Report {\npublic:\n void generate() { /* création des données */ }\n void saveToFile(const std::string& path) { /* écriture dans un fichier */ }\n void sendEmail(const std::string& to) { /* envoi */ }\n};\nIci, Report est responsable à la fois de la génération, de la sauvegarde et de l'envoi — trois raisons de changer.
Refactorisation avec SRP
\nclass Report {\npublic:\n void generate() { /* création des données */ }\n};\n\nclass ReportSaver {\npublic:\n void save(const Report& report, const std::string& path);\n};\n\nclass EmailSender {\npublic:\n void send(const Report& report, const std::string& to);\n};\nDésormais, chaque classe a une responsabilité unique. Cela simplifie les tests unitaires et la réutilisation.
\n\n2. Open/Closed Principle (OCP) — Principe d'ouverture/fermeture
\nFormulation : Les entités logicielles doivent être ouvertes à l'extension, mais fermées à la modification.
\nEn C++, cela est souvent implémenté via le polymorphisme (fonctions virtuelles) ou via les templates (CRTP). Pour le niveau avancé, examinons les deux techniques.
\nApproche polymorphe (fonctions virtuelles)
\nclass Shape {\npublic:\n virtual double area() const = 0;\n virtual ~Shape() = default;\n};\n\nclass Circle : public Shape {\n double r;\npublic:\n Circle(double r) : r(r) {}\n double area() const override { return 3.14 * r * r; }\n};\n\nclass Rectangle : public Shape {\n double w, h;\npublic:\n Rectangle(double w, double h) : w(w), h(h) {}\n double area() const override { return w * h; }\n};\nL'ajout d'une nouvelle forme (par exemple, Triangle) ne nécessite pas de modifier les classes existantes — seulement la création d'un nouvel héritier.
Approche par template (CRTP — Curiously Recurring Template Pattern)
\ntemplate<typename Derived>\nclass ShapeBase {\npublic:\n double area() const { return static_cast<const Derived*>(this)->area_impl(); }\n};\n\nclass Circle : public ShapeBase<Circle> {\n double r;\npublic:\n Circle(double r) : r(r) {}\n double area_impl() const { return 3.14 * r * r; }\n};\nLe CRTP offre un polymorphisme statique sans les frais généraux des appels virtuels, ce qui est crucial pour le C++ haute performance.
\n\n3. Liskov Substitution Principle (LSP) — Principe de substitution de Barbara Liskov
\nFormulation : Les objets de la classe de base doivent pouvoir être remplacés par des objets des classes dérivées sans altérer la correction du programme.
\nLa violation classique du LSP en C++ est la redéfinition d'une méthode virtuelle avec un affaiblissement des préconditions ou un renforcement des postconditions. Prenons l'exemple de la hiérarchie « Rectangle — Carré ».
\nAnti-patron
\nclass Rectangle {\nprotected:\