Princípios SOLID: escreva código que não vira bagunça

23 de fevereiro de 2026

SOLID é um acrônimo criado por Robert C. Martin (o "Uncle Bob") para resumir 5 princípios de design que tornam o código orientado a objetos mais fácil de entender, manter e estender. Vamos ver cada um com exemplos práticos usando os personagens que você já conhece: Maya, Pancho e Romeu.

S — Single Responsibility Principle (Responsabilidade Única)

Uma classe deve ter apenas um motivo para mudar.

Em outras palavras: cada classe deve fazer uma coisa só, e fazê-la bem.

Errado — a classe Cachorro está fazendo coisas demais:

class Cachorro: def __init__(self, nome: str): self.nome = nome def latir(self) -> str: return f"{self.nome} diz: Au au!" def salvar_no_banco(self) -> None: # lógica de banco de dados misturada com lógica de negócio print(f"Salvando {self.nome} no banco...") def enviar_email_dono(self) -> None: # lógica de e-mail misturada aqui também print(f"Enviando e-mail sobre {self.nome}...")

Certo — cada classe tem sua responsabilidade:

class Cachorro: def __init__(self, nome: str): self.nome = nome def latir(self) -> str: return f"{self.nome} diz: Au au!" class CachorroRepositorio: def salvar(self, cachorro: Cachorro) -> None: print(f"Salvando {cachorro.nome} no banco...") class NotificacaoDono: def enviar_email(self, cachorro: Cachorro) -> None: print(f"Enviando e-mail sobre {cachorro.nome}...")

Se a lógica do banco mudar, você altera apenas CachorroRepositorio. A classe Cachorro não precisa nem saber que um banco existe.

O — Open/Closed Principle (Aberto/Fechado)

Uma classe deve estar aberta para extensão, mas fechada para modificação.

Você não deveria precisar editar uma classe que já funciona só para adicionar um novo comportamento.

Errado — toda vez que um novo animal aparecer, você precisa modificar calcular_som:

class Animal: def __init__(self, tipo: str, nome: str): self.tipo = tipo self.nome = nome def calcular_som(animal: Animal) -> str: if animal.tipo == "cachorro": return "Au au!" elif animal.tipo == "gato": return "Miau!" # Adicionou um novo animal? Precisa editar essa função.

Certo — cada animal define seu próprio comportamento, sem mexer no código existente:

from abc import ABC, abstractmethod class Animal(ABC): def __init__(self, nome: str): self.nome = nome @abstractmethod def fazer_som(self) -> str: pass class Cachorro(Animal): def fazer_som(self) -> str: return "Au au!" class Gato(Animal): def fazer_som(self) -> str: return "Miau!" class Passaro(Animal): # novo animal, zero alteração nas classes anteriores def fazer_som(self) -> str: return "Piu piu!"

Quer adicionar um papagaio? Crie uma nova classe. Nada existente quebra.

L — Liskov Substitution Principle (Substituição de Liskov)

Objetos de uma subclasse devem poder substituir objetos da superclasse sem quebrar o programa.

Se Cachorro herda de Animal, em qualquer lugar que você usa Animal, você deve poder colocar um Cachorro sem surpresas.

ErradoCachorroMudo quebra o contrato da superclasse:

class Animal: def fazer_som(self) -> str: return "..." class Cachorro(Animal): def fazer_som(self) -> str: return "Au au!" class CachorroMudo(Animal): def fazer_som(self) -> str: raise NotImplementedError("Este cachorro não faz som!") # Substituir Animal por CachorroMudo quebra o código.

Certo — a hierarquia de classes reflete a realidade:

from abc import ABC, abstractmethod class Animal(ABC): def __init__(self, nome: str): self.nome = nome @abstractmethod def fazer_som(self) -> str: pass class AnimalComVoz(Animal): pass class AnimalSilencioso(Animal): def fazer_som(self) -> str: return "" # retorna string vazia — contrato mantido class Cachorro(AnimalComVoz): def fazer_som(self) -> str: return "Au au!" class Peixe(AnimalSilencioso): pass

Agora você pode iterar sobre qualquer lista de Animal sem se preocupar com exceções inesperadas.

I — Interface Segregation Principle (Segregação de Interfaces)

Uma classe não deve ser forçada a implementar métodos que ela não usa.

Interfaces grandes demais obrigam classes a implementar coisas que não fazem sentido para elas.

ErradoPeixe é obrigado a implementar voar:

from abc import ABC, abstractmethod class Animal(ABC): @abstractmethod def fazer_som(self) -> str: pass @abstractmethod def voar(self) -> str: pass @abstractmethod def nadar(self) -> str: pass class Peixe(Animal): def fazer_som(self) -> str: return "" def voar(self) -> str: raise NotImplementedError("Peixe não voa!") # absurdo def nadar(self) -> str: return "Splish splash!"

Certo — interfaces menores e específicas:

from abc import ABC, abstractmethod class Falante(ABC): @abstractmethod def fazer_som(self) -> str: pass class Voador(ABC): @abstractmethod def voar(self) -> str: pass class Nadador(ABC): @abstractmethod def nadar(self) -> str: pass class Peixe(Falante, Nadador): def fazer_som(self) -> str: return "" def nadar(self) -> str: return "Splish splash!" class Passaro(Falante, Voador): def fazer_som(self) -> str: return "Piu piu!" def voar(self) -> str: return "Flap flap!"

Maya (cachorro) não precisa saber nadar na interface. Romeu (gato) não precisa saber voar. Cada classe implementa apenas o que faz sentido para ela.

D — Dependency Inversion Principle (Inversão de Dependência)

Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.

Em vez de depender de implementações concretas, dependa de interfaces.

ErradoTreinadorDeCachorros está acoplado diretamente à classe Cachorro:

class Cachorro: def sentar(self) -> str: return "sentou!" class TreinadorDeCachorros: def __init__(self): self.animal = Cachorro() # dependência concreta — difícil de trocar def treinar(self) -> str: return self.animal.sentar()

Certo — o treinador depende de uma abstração, não de uma implementação:

from abc import ABC, abstractmethod class AnimalTreinavel(ABC): @abstractmethod def executar_comando(self, comando: str) -> str: pass class Cachorro(AnimalTreinavel): def __init__(self, nome: str): self.nome = nome def executar_comando(self, comando: str) -> str: return f"{self.nome} obedeceu: {comando}!" class Treinador: def __init__(self, animal: AnimalTreinavel): # depende da abstração self.animal = animal def treinar(self, comando: str) -> str: return self.animal.executar_comando(comando) maya = Cachorro("Maya") pancho = Cachorro("Pancho") treinador = Treinador(maya) print(treinador.treinar("sentar")) # Maya obedeceu: sentar! treinador.animal = pancho print(treinador.treinar("deitar")) # Pancho obedeceu: deitar!

Amanhã você pode criar um Gato treinável ou até um Robo treinável — o Treinador não precisa mudar nada.

Resumo rápido

PrincípioSiglaRegra principal
Single ResponsibilitySUma classe, um motivo para mudar
Open/ClosedOAberta para extensão, fechada para modificação
Liskov SubstitutionLSubclasses devem substituir a superclasse sem surpresas
Interface SegregationIPrefira interfaces pequenas e específicas
Dependency InversionDDependa de abstrações, não de implementações

Conclusão

SOLID não é um checklist rígido a seguir cegamente — é um conjunto de diretrizes que, quando bem aplicado, resulta em código mais flexível, testável e fácil de manter.

No início pode parecer que você está criando classes demais para pouca coisa. Mas quando o projeto cresce e os requisitos mudam (e eles sempre mudam), você vai agradecer por ter seguido esses princípios desde o começo.

Se você ainda não viu o post sobre os pilares da POO, vale a pena começar por lá — os conceitos de herança e abstração são base para entender o SOLID com profundidade.

GitHub
LinkedIn