Introdução: SOLID no contexto de C++
SOLID é um acrônimo de cinco princípios fundamentais de design orientado a objetos, formulados por Robert Martin. Para C++, como uma linguagem com tipagem estática poderosa, herança múltipla e RAII, esses princípios adquirem um significado especial. Em nível avançado, a compreensão do SOLID permite criar sistemas flexíveis, testáveis e extensíveis, evitando antipadrões como "classe divina" ou acoplamento rígido.
Neste artigo, analisaremos cada princípio com exemplos práticos de código C++ (17/20), focando nas nuances de implementação: uso de std::unique_ptr, std::function, CRTP, destrutores virtuais e expressões idiomáticas modernas.
1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
Formulação: Uma classe deve ter apenas um motivo para ser alterada.
Na prática, isso significa que a classe é responsável por uma entidade de negócio ou uma única operação. A violação do SRP leva ao "acoplamento" de responsabilidades, dificultando os testes e a modificação.
Exemplo de violação
class Report {public: void generate() { /* criação de dados */ } void saveToFile(const std::string& path) { /* gravação em arquivo */ } void sendEmail(const std::string& to) { /* envio */ }};Aqui, Report é responsável pela geração, salvamento e envio — três motivos para alteração.
Refatoração com SRP
class Report {public: void generate() { /* criação de dados */ }};
class ReportSaver {public: void save(const Report& report, const std::string& path);};
class EmailSender {public: void send(const Report& report, const std::string& to);};Agora cada classe tem uma única responsabilidade. Isso simplifica os testes unitários e o reuso.
2. Open/Closed Principle (OCP) — Princípio Aberto/Fechado
Formulação: Entidades de software devem estar abertas para extensão, mas fechadas para modificação.
Em C++, isso é frequentemente implementado através de polimorfismo (funções virtuais) ou templates (CRTP). Para nível avançado, consideraremos ambas as técnicas.
Abordagem polimórfica (funções virtuais)
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; }};Adicionar uma nova forma (por exemplo, Triangle) não requer alteração das classes existentes — apenas a criação de um novo herdeiro.
Abordagem com template (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 fornece polimorfismo estático sem a sobrecarga de chamadas virtuais, o que é crítico para C++ de alto desempenho.
3. Liskov Substitution Principle (LSP) — Princípio da Substituição de Barbara Liskov
Formulação: Objetos de uma classe base devem ser substituíveis por objetos de classes derivadas sem quebrar a correção do programa.
A violação clássica do LSP em C++ é a sobrescrita de um método virtual com enfraquecimento de pré-condições ou fortalecimento de pós-condições. Vamos considerar um exemplo com a hierarquia "Retângulo — Quadrado".
Antipadrão
class Rectangle {protected:\