Introducción: SOLID en el contexto de C++
SOLID es el acrónimo de cinco principios fundamentales del diseño orientado a objetos, formulados por Robert Martin. Para C++, como lenguaje con potente tipado estático, herencia múltiple y RAII, estos principios adquieren especial relevancia. A nivel avanzado, comprender SOLID permite crear sistemas flexibles, comprobables y extensibles, evitando antipatrones como la «clase divina» o el acoplamiento rígido.
En este artículo analizaremos cada principio con ejemplos prácticos de código C++ (17/20), centrándonos en los matices de implementación: uso de std::unique_ptr, std::function, CRTP, destructores virtuales y modismos modernos.
1. Single Responsibility Principle (SRP) — principio de responsabilidad única
Formulación: Una clase debe tener solo una razón para cambiar.
En la práctica, esto significa que la clase es responsable de una entidad de negocio o una sola operación. La violación de SRP conduce al «acoplamiento» de responsabilidades, lo que dificulta las pruebas y la modificación.
Ejemplo de violación
class Report {public: void generate() { /* creación de datos */ } void saveToFile(const std::string& path) { /* escritura en archivo */ } void sendEmail(const std::string& to) { /* envío */ }};Aquí Report es responsable tanto de la generación, como del guardado y del envío — tres razones para cambiar.
Refactorización con SRP
class Report {public: void generate() { /* creación de datos */ }};
class ReportSaver {public: void save(const Report& report, const std::string& path);};
class EmailSender {public: void send(const Report& report, const std::string& to);};Ahora cada clase tiene una única responsabilidad. Esto simplifica las pruebas unitarias y la reutilización.
2. Open/Closed Principle (OCP) — principio de abierto/cerrado
Formulación: Las entidades de software deben estar abiertas para la extensión, pero cerradas para la modificación.
En C++, esto a menudo se implementa mediante polimorfismo (funciones virtuales) o mediante plantillas (CRTP). Para nivel avanzado, consideraremos ambas técnicas.
Enfoque polimórfico (funciones virtuales)
class Shape {public: virtual double area() const = 0; virtual ~Shape() = default;};
class Circle : public Shape { double r;public: Circle(double r) : r(r) {} double area() const override { return 3.14 * r * r; }};
class Rectangle : public Shape { double w, h;public: Rectangle(double w, double h) : w(w), h(h) {} double area() const override { return w * h; }};Agregar una nueva figura (por ejemplo, Triangle) no requiere modificar las clases existentes — solo crear un nuevo heredero.
Enfoque con plantillas (CRTP — Curiously Recurring Template Pattern)
template<typename Derived>class ShapeBase {public: double area() const { return static_cast<const Derived*>(this)->area_impl(); }};
class Circle : public ShapeBase<Circle> { double r;public: Circle(double r) : r(r) {} double area_impl() const { return 3.14 * r * r; }};CRTP proporciona polimorfismo estático sin la sobrecarga de las llamadas virtuales, lo cual es crítico para C++ de alto rendimiento.
3. Liskov Substitution Principle (LSP) — principio de sustitución de Barbara Liskov
Formulación: Los objetos de una clase base deben poder ser reemplazados por objetos de sus clases derivadas sin alterar la corrección del programa.
La violación clásica de LSP en C++ es la redefinición de un método virtual debilitando las precondiciones o fortaleciendo las postcondiciones. Consideremos un ejemplo con la jerarquía «Rectángulo — Cuadrado».
Antipatrón
class Rectangle {protected:\