Por que SOLID existe?
SOLID é um acrônimo para cinco princípios de projeto orientado a objetos popularizados por Robert C. Martin: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation e Dependency Inversion. Eles ajudam a responder uma pergunta prática: quando um requisito mudar, como evitar que a alteração se espalhe por componentes que não deveriam ser afetados?
Os princípios são heurísticas, não leis físicas. Segui-los literalmente sem observar o contexto pode produzir excesso de abstrações. O valor está em identificar pontos de mudança, separar responsabilidades e tornar contratos honestos. Eles se conectam diretamente à Clean Architecture e à Dependency Rule, mas podem ser usados em qualquer estilo arquitetural.
1. Single Responsibility Principle (SRP)
Uma classe deve ter uma responsabilidade coesa — ou, numa formulação útil, um motivo principal para mudar. “Responsabilidade” não significa que a classe só pode ter um método; significa que suas decisões pertencem a uma preocupação coerente.
Uma classe RelatorioService que calcula indicadores, consulta SQL, produz PDF e envia e-mail acumula motivos de mudança de negócio, banco, formato de documento e comunicação. Separar essas preocupações permite alterar a forma do relatório sem refazer a persistência. A separação só compensa se esses aspectos realmente mudam de forma independente; criar uma classe para cada linha aumentaria navegação sem reduzir acoplamento.
2. Open/Closed Principle (OCP)
Um módulo deve estar aberto para extensão e fechado para modificação nos pontos em que uma variação previsível precisa ser acomodada. Isso não quer dizer nunca editar código existente. Significa que uma nova variante não deveria obrigar a reescrever uma lógica estável e repetidamente sensível a erros.
Se o cálculo de frete tem estratégias distintas por modalidade, uma abstração ICalculadoraFrete pode permitir implementações específicas. Mas criar um plugin framework para duas regras simples pode ser pior que um switch explícito. O sinal de que OCP ajuda é a recorrência de mudanças no mesmo ponto e a possibilidade de definir um contrato estável para as extensões.
3. Liskov Substitution Principle (LSP)
Uma implementação substituta precisa respeitar as expectativas estabelecidas pelo contrato. Se uma interface promete que uma operação grava dados e retorna sucesso somente depois da confirmação, uma implementação que apenas coloca os dados numa fila sem indicar a diferença quebra a substituição, mesmo compilando.
O problema clássico da herança é criar subtipos que não conseguem cumprir as garantias do tipo base. Prefira contratos cujas precondições e resultados sejam claros. Tipos podem compartilhar comportamento por composição sem fingir que um é especialização do outro. Testes de contrato ajudam quando várias implementações precisam cumprir o mesmo protocolo.
4. Interface Segregation Principle (ISP)
Consumidores não devem ser obrigados a depender de operações que não usam. Uma interface ampla como IRepositorio com dezenas de métodos pode forçar uma implementação simples a lançar NotSupportedException em operações irrelevantes. Contratos menores e orientados às necessidades dos consumidores deixam as dependências mais explícitas.
Segregar não significa criar uma interface por método. Agrupe operações que mudam juntas e pertencem ao mesmo papel. Por exemplo, uma consulta de leitura pode ser um contrato diferente de uma porta de gravação se os consumidores, requisitos de desempenho ou ciclos de mudança forem distintos. Evite fragmentação que torne difícil descobrir a capacidade completa de um componente.
5. Dependency Inversion Principle (DIP)
Módulos de alto nível não devem depender diretamente de módulos de baixo nível; ambos devem depender de abstrações estáveis. Detalhes devem implementar essas abstrações. A inversão está na direção das referências do código, não necessariamente no fluxo de execução.
Um caso de uso pode declarar IRepositorioPedidos e o adaptador SQL implementá-la. O caso de uso não importa Entity Framework, embora em tempo de execução chame uma implementação construída com esse framework. A injeção de dependência conecta os objetos; a arquitetura limpa ajuda a colocar a abstração no limite correto. Para ver o fluxo completo de ponta a ponta, consulte o guia de Clean Architecture com exemplo em C#.
Como usar SOLID sem overengineering
Comece pelo comportamento e pela mudança que precisa ser suportada. Pergunte se uma classe tem motivos independentes para mudar, se um contrato representa uma capacidade real e se a abstração protege uma política de um detalhe volátil. Prefira a solução mais simples que mantenha essas decisões compreensíveis.
Não introduza interface só para permitir um mock; muitas vezes uma função pura ou um teste de integração é melhor. Não use herança para compartilhar poucas linhas de código se composição expressa melhor a relação. Não extraia uma estratégia extensível antes de haver uma variação concreta ou provável. SOLID deve reduzir o custo total de mudança, incluindo o custo de entender o sistema.
Resumo
SRP organiza motivos de mudança; OCP protege pontos de extensão; LSP mantém substituições honestas; ISP reduz contratos desnecessários; DIP protege políticas de detalhes. Juntos, orientam código coeso e dependências deliberadas. Para conectar os princípios a domínio, casos de uso e infraestrutura, continue pelo artigo de arquitetura limpa e pelo guia de Domain-Driven Design.