Provavelmente você já ouviu pelo menos uma vez em uma entrevista: “Você pode explicar os princípios SOLID?”
Mas, falando a verdade, embora muita gente consiga recitar os cinco princípios, poucas pessoas chegam à pergunta: “Então, como aplico isso no meu código?”
Desta vez, vou explicar SOLID por meio de situações práticas, e não de definições de livro. Como há bastante conteúdo, vou dividir em duas partes. Hoje veremos as três primeiras letras: SRP, OCP e LSP.
SOLID é o acrônimo dos cinco princípios de design orientado a objetos organizados por Robert Martin (Uncle Bob).
- SRP — Princípio da responsabilidade única
- OCP — Princípio aberto/fechado
- LSP — Princípio da substituição de Liskov
- ISP — Princípio da segregação de interfaces
- DIP — Princípio da inversão de dependência
Os cinco princípios parecem tratar de assuntos diferentes, mas têm uma mensagem em comum: reduzir o escopo do código que precisa ser alterado quando ocorre uma mudança.
SRP — Princípio da responsabilidade única
A definição de livro diz que “uma classe deve ter apenas uma responsabilidade”, mas o próprio Uncle Bob reformulou isso depois de uma maneira melhor.
Um módulo deve ser responsável por um único ator.
Aqui, ator é a pessoa ou o departamento que solicita mudanças nesse código. Vamos supor que exista uma classe assim.
class Employee {
func calculatePay() { ... } // O time de contabilidade pediu uma alteração
func reportHours() { ... } // O time de RH pediu uma alteração
func save() { ... } // O time de desenvolvimento(DBA)pediu uma alteração
}
Os três métodos têm responsáveis diferentes. Se, ao alterar calculatePaya pedido da contabilidade, você mexer na lógica compartilhada, o relatório de RH pode ficar silenciosamente errado. Quando isso acontece na prática, também é difícil encontrar a causa.
O SRP resume isso assim: “Se as pessoas que pedem alterações são diferentes, separe o código também”. Ao dividir cálculo de folha, relatório de trabalho e persistência em classes diferentes, uma solicitação da contabilidade não afeta a funcionalidade de RH.
OCP — Princípio aberto/fechado
A definição é: “aberto para extensão e fechado para modificação”. No começo parece um enigma, mas fica claro quando vemos o código.
// Se for necessário modificar esta função sempre que um meio de pagamento for adicionado
func pay(method: String, amount: Int) {
if method == "card" { ... }
else if method == "kakaopay" { ... }
else if method == "naverpay" { ... }
// Novo meio de pagamento = modificar else if = o código existente
}
Nessa estrutura, para adicionar o Toss Pay, é preciso abrir e modificar uma função que já funcionava. A cada alteração, você assume o risco de quebrar os meios de pagamento existentes.
// Uma estrutura em que basta “adicionar” um novo meio de pagamento
let payments: [String: Payment] = [
"card": CardPayment(),
"kakaopay": KakaoPayment(),
"naverpay": NaverPayment(),
// Adicionar Toss Pay = registrar uma nova classe em uma linha
]
func pay(method: String, amount: Int) {
payments[method]?.process(amount: amount)
}
O código existente não é alterado (fechado para modificação), e a funcionalidade cresce apenas com a adição de uma nova classe (aberto para extensão). O segredo é aplicar essa estrutura somente nos pontos que mudam com frequência. Aplicá-la a todo o código violaria YAGNI, como vimos no artigo anterior.
LSP — Princípio da substituição de Liskov
O nome parece o mais assustador, mas o significado é simples.
Uma classe filha deve poder substituir a classe pai onde quer que ela seja usada sem quebrar o programa.
O contraexemplo famoso é o problema do retângulo e do quadrado. Se herdarmos seguindo o senso comum matemático de que “um quadrado é um retângulo”, isso acontece.
class Rectangle {
var width = 0
var height = 0
func setWidth(_ w: Int) { width = w }
func setHeight(_ h: Int) { height = h }
}
class Square: Rectangle {
override func setWidth(_ w: Int) { width = w; height = w } // Por ser um quadrado
override func setHeight(_ h: Int) { width = h; height = h }
}
// Código que espera um retângulo
func test(_ rect: Rectangle) {
rect.setWidth(5)
rect.setHeight(4)
print(rect.width * rect.height) // 20espera Squaremas, se for um quadrado 16
}
No momento em que você passa Square, quebra-se a expectativa óbvia de que “com largura 5 e altura 4, a área é 20”. Mesmo que a relação is-a pareça válida, LSP diz que não se deve usar herança quando o contrato de comportamento é quebrado.
O sinal prático é este: se uma classe filha sobrescreve um método do pai para lançar uma exceção ou o deixa vazio, é muito provável que essa herança viole o LSP.
Encerrando a primeira parte
Vamos deixar uma frase para cada um dos três princípios abordados hoje.
- SRP — Se as pessoas que pedem alterações são diferentes, separe o código também.
- OCP — Faça com que os pontos que mudam com frequência possam ser tratados com adições em vez de modificações.
- LSP — A classe filha não deve quebrar os contratos da classe pai. Se precisar quebrá-los, a herança está errada.
No próximo artigo, vou abordar as duas letras restantes: ISP (segregação de interfaces) e DIP (inversão de dependência). O DIP será especialmente interessante porque se conecta diretamente ao motivo pelo qual os frameworks atuais usam injeção de dependência.

![Imagem de capa de [SOLID #1] As cinco letras de SOLID: entenda em vez de decorar (SRP · OCP · LSP)](/assets/images/posts/7b95a0fb-b586-4f62-b56e-4a8b31a29dfd/1.jpg)