Ao escrever código, é comum começar com algo que funciona, mas conforme o projeto cresce, surgem situações como:
- Uma classe que cresce para centenas de linhas e ninguém quer mexer — a \u201Cclasse Deus\u201D
- Código que você mesmo escreveu há meses e não consegue mais entender
- Cada nova funcionalidade exige alterar código existente, e adicionar algo novo vira motivo de medo
- Corrigir um lugar quebra outro sem motivo aparente
Isso não é falta de habilidade individual — é um problema de design. Se não pensarmos na \u201Cestrutura\u201D do código, além da sintaxe, o projeto inevitavelmente se torna complexo e impossível de manter.
Os princípios SOLID foram propostos justamente para evitar esse cenário. SOLID reúne cinco princípios fundamentais do design orientado a objetos, sistematizados por Robert C. Martin (Uncle Bob) no início dos anos 2000. Hoje, são usados como base de design em praticamente todo desenvolvimento de software: web, IA/ML, jogos, embarcados e muito mais.
Este guia explica cada um dos cinco princípios: por que existem, comparações entre design ruim e bom, exemplos em Python e aplicações práticas. Os conceitos valem para qualquer linguagem, mas os exemplos usam Python.
Este artigo assume que você conhece o básico de orientação a objetos (classes, herança, métodos). Para começar com Python, veja o Guia de Introdução ao Python. Para design de tratamento de erros, confira 7 Padrões de Tratamento de Erros em Python.
Visão geral dos princípios SOLID
| Letra | Nome | Em uma frase |
| S | Single Responsibility Principle (Responsabilidade Única) | Uma classe, um papel. Um único motivo para mudar |
| O | Open/Closed Principle (Aberto/Fechado) | Aberto para extensão, fechado para modificação |
| L | Liskov Substitution Principle (Substituição de Liskov) | Subclasses podem substituir a classe pai |
| I | Interface Segregation Principle (Segregação de Interfaces) | Não force funcionalidades desnecessárias |
| D | Dependency Inversion Principle (Inversão de Dependências) | Dependa de abstrações, não de implementações concretas |
Em resumo, SOLID são regras de design para escrever código resiliente a mudanças, que quebra menos e se estende com mais facilidade.
Por que os princípios SOLID são necessários
Para entender a importância do SOLID, veja um exemplo de design ruim:
class UserManager:
def register(self, name, email):
# cadastro de usuário
pass
def send_welcome_email(self, email):
# envio de e-mail
pass
def save_to_database(self, user):
# persistência em DB
pass
def generate_monthly_report(self):
# geração de relatório
pass
def validate_input(self, data):
# validação de entrada
pass
Essa classe parece conveniente, mas tem problemas sérios:
| Problema | Impacto |
| Mais de cinco responsabilidades | Qualquer mudança exige alterar esta classe |
| Alcance amplo das mudanças | Corrigir envio de e-mail pode afetar persistência |
| Testes difíceis | Para testar envio de e-mail, precisa de conexão com DB |
| Reuso limitado | Não dá para usar só o envio de e-mail em outro projeto |
| Conflitos em equipe | Todos editam o mesmo arquivo, merges frequentes |
É um design em que \u201Cuma mudança quebra tudo\u201D. Quanto maior o projeto, pior fica. SOLID existe para evitar isso.
Com SOLID, o cenário muda:
| Sem SOLID | Com SOLID |
| Não se sabe o que quebra ao corrigir | Alcance claro e correções mais seguras |
| Novas funcionalidades exigem alterar código antigo | Extensão sem modificar o que já existe |
| Testes difíceis ou inviáveis | Cada classe pode ser testada isoladamente |
| Código ilegível | Responsabilidades claras e leitura mais fácil |
S: Responsabilidade Única (Single Responsibility Principle)
Uma classe, um papel. A classe deve ter apenas um motivo para ser alterada.
É o princípio mais básico e importante do SOLID. \u201CResponsabilidade\u201D pode ser entendida como \u201Cmotivo de mudança\u201D. Se uma classe tem vários motivos para mudar, ela está com responsabilidades demais.
Exemplo ruim: responsabilidades misturadas
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def save(self):
# persistir no DB (responsabilidade de persistência)
pass
def send_email(self, subject, body):
# envio de e-mail (responsabilidade de notificação)
pass
def calculate_age(self, birthdate):
# cálculo de idade (responsabilidade de regra de negócio)
pass
Essa classe tem três motivos de mudança: alteração de esquema de DB, mudança no serviço de e-mail e mudança na lógica de cálculo de idade. Qualquer um pode impactar os outros.
Exemplo bom: responsabilidades separadas
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserRepository:
def save(self, user: User):
# persistir no DB
pass
class EmailService:
def send(self, to: str, subject: str, body: str):
# envio de e-mail
pass
class AgeCalculator:
def calculate(self, birthdate) -> int:
# cálculo de idade
pass
| Classe | Responsabilidade | Motivo de mudança |
| User | Manter dados do usuário | Apenas quando a estrutura de dados mudar |
| UserRepository | Persistência (DB) | Mudanças em esquema ou ORM |
| EmailService | Envio de e-mail | Mudanças no serviço de e-mail |
| AgeCalculator | Regra de negócio | Mudanças na lógica de cálculo |
Responsabilidade única não significa \u201Cuma classe, um método\u201D. Significa \u201Cum único motivo para mudar\u201D. Ter vários métodos para cumprir uma responsabilidade é aceitável.
Aplicar SRP de forma exagerada pode gerar muitas classes e dificultar a manutenção. O importante é manter \u201Cum motivo de mudança\u201D com um nível de granularidade razoável.
O: Aberto/Fechado (Open/Closed Principle)
O software deve estar aberto para extensão e fechado para modificação.
Ou seja: novas funcionalidades devem ser adicionadas sem alterar o código existente. Modificar código antigo aumenta o risco de bugs. Se for possível estender sem modificar, o risco de quebrar o que já funciona cai.
Exemplo ruim: extensão via condicionais
class DiscountCalculator:
def calculate(self, customer_type: str, price: float) -> float:
if customer_type == \u0022normal\u0022:
return price * 0.9
elif customer_type == \u0022vip\u0022:
return price * 0.8
elif customer_type == \u0022super_vip\u0022:
return price * 0.7
# cada novo tipo exige alterar aqui...
Cada novo tipo de cliente exige alterar esse método. Cada alteração pode quebrar os cálculos já existentes.
Exemplo bom: extensão por herança e polimorfismo
from abc import ABC, abstractmethod
class Discount(ABC):
@abstractmethod
def calculate(self, price: float) -> float:
pass
class NormalDiscount(Discount):
def calculate(self, price: float) -> float:
return price * 0.9
class VipDiscount(Discount):
def calculate(self, price: float) -> float:
return price * 0.8
# novo tipo de desconto sem alterar o existente
class SuperVipDiscount(Discount):
def calculate(self, price: float) -> float:
return price * 0.7
Para adicionar um novo tipo de desconto, basta criar uma nova classe. As classes NormalDiscount e VipDiscount permanecem intactas.
O princípio Aberto/Fechado é usado em frameworks web em Python. Middlewares do Django e Blueprints do Flask permitem adicionar funcionalidades sem alterar o núcleo do framework.
L: Substituição de Liskov (Liskov Substitution Principle)
Onde a classe pai é usada, a subclasse deve poder substituí-la sem quebrar o comportamento esperado.
Herança deve ser usada quando há relação \u201Cis-a\u201D (X é um tipo de Y). Se a subclasse quebra o contrato da classe pai, o código que a usa pode falhar de forma inesperada.
Exemplo ruim: herança que quebra o contrato
class Bird:
def fly(self):
return \u0022Flying!\u0022
class Penguin(Bird):
def fly(self):
raise Exception(\u0022Penguins can't fly!\u0022)
# código que usa quebra
def make_bird_fly(bird: Bird):
return bird.fly() # exceção se receber Penguin
Penguin herda de Bird, mas quebra o contrato de fly(). Ao passar um Penguin para código que espera Bird, ocorre exceção.
Exemplo bom: hierarquia adequada
class Bird:
def eat(self):
return \u0022Eating!\u0022
class FlyingBird(Bird):
def fly(self):
return \u0022Flying!\u0022
class Penguin(Bird):
def swim(self):
return \u0022Swimming!\u0022
class Eagle(FlyingBird):
pass
# seguro: fly só é exigido de FlyingBird
def make_bird_fly(bird: FlyingBird):
return bird.fly()
Separando \u201Cave\u201D e \u201Cave que voa\u201D, Penguin não precisa implementar fly().
\u201CQuadrado é um retângulo\u201D é matematicamente verdadeiro, mas em código pode violar LSP. Se Square herda de Rectangle, Square pode quebrar o contrato de alterar largura e altura de forma independente. Esse é o famoso \u201Cproblema quadrado-retângulo\u201D.
I: Segregação de Interfaces (Interface Segregation Principle)
O cliente não deve ser obrigado a depender de métodos que não usa.
Em vez de uma interface grande, prefira interfaces menores, com apenas o que cada cliente precisa.
Exemplo ruim: interface grande demais
from abc import ABC, abstractmethod
class Worker(ABC):
@abstractmethod
def work(self):
pass
@abstractmethod
def eat(self):
pass
@abstractmethod
def sleep(self):
pass
class Robot(Worker):
def work(self):
return \u0022Working!\u0022
def eat(self):
raise NotImplementedError(\u0022Robots don't eat\u0022)
def sleep(self):
raise NotImplementedError(\u0022Robots don't sleep\u0022)
Robot precisa implementar eat() e sleep(), mas robôs não comem nem dormem. A interface força métodos desnecessários.
Exemplo bom: interfaces segregadas
from abc import ABC, abstractmethod
class Workable(ABC):
@abstractmethod
def work(self):
pass
class Eatable(ABC):
@abstractmethod
def eat(self):
pass
class Sleepable(ABC):
@abstractmethod
def sleep(self):
pass
class Human(Workable, Eatable, Sleepable):
def work(self):
return \u0022Working!\u0022
def eat(self):
return \u0022Eating!\u0022
def sleep(self):
return \u0022Sleeping!\u0022
class Robot(Workable):
def work(self):
return \u0022Working!\u0022
Robot implementa apenas Workable. Human implementa as três. Robot não é forçado a ter eat() ou sleep().
Em Python, ISP costuma ser aplicado com herança múltipla (padrão Mixin). Em Java ou C# usa-se interface; em Python, ABCs combinados.
D: Inversão de Dependências (Dependency Inversion Principle)
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 (MySQL, SendGrid etc.), dependa de abstrações (interfaces). Assim fica mais fácil trocar implementações.
Exemplo ruim: dependência direta da implementação
class MySQLDatabase:
def save(self, data):
# salvar no MySQL
pass
class UserService:
def __init__(self):
self.db = MySQLDatabase() # dependência direta
def create_user(self, name):
self.db.save({\u0022name\u0022: name})
UserService depende diretamente de MySQL. Para migrar para PostgreSQL, seria preciso alterar UserService. Também não dá para usar um DB mock nos testes.
Exemplo bom: dependência de abstração + injeção de dependência
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def save(self, data):
pass
class MySQLDatabase(Database):
def save(self, data):
# salvar no MySQL
pass
class PostgreSQLDatabase(Database):
def save(self, data):
# salvar no PostgreSQL
pass
class UserService:
def __init__(self, db: Database): # depende da abstração
self.db = db
def create_user(self, name):
self.db.save({\u0022name\u0022: name})
# uso
service = UserService(MySQLDatabase())
# trocar DB é simples
service = UserService(PostgreSQLDatabase())
UserService depende da abstração Database. MySQL, PostgreSQL ou um mock de teste podem ser injetados sem alterar UserService.
Inversão de dependências costuma vir junto com Injeção de Dependência (DI): em vez de criar o objeto dentro da classe, recebê-lo por parâmetro. Em Python, passar no __init__ é a forma mais simples de DI.
Criar abstrações para todas as classes é excesso. Use abstração onde faz sentido trocar implementação. Para utilitários estáveis, muitas vezes não vale a pena.
Antes e depois de aplicar SOLID
| Aspecto | Sem SOLID | Com SOLID |
| Segurança ao corrigir | Impacto incerto | Alcance claro e correções mais seguras |
| Novas funcionalidades | Alteração constante do código existente | Novas classes, sem mexer no antigo |
| Testes | Muitas dependências, difícil testar | Classes isoladas e testáveis |
| Legibilidade | Classes enormes, difícil entender | Classes menores, responsabilidades claras |
| Trabalho em equipe | Todos editam os mesmos arquivos | Responsabilidades separadas, menos conflitos |
| Manutenção a longo prazo | Dívida técnica acumulada | Estrutura sustentável |
No curto prazo, aplicar SOLID pode parecer trabalhoso (mais classes). Com o tempo, o retorno aparece: projetos maiores se beneficiam bastante.
Uso na prática
SOLID não é só teoria; está presente em boa parte do desenvolvimento moderno.
| Tecnologia/Arquitetura | Princípios SOLID | Exemplo |
| Frameworks web | OCP, DIP | Middlewares do Django, injeção de dependência do FastAPI |
| Microsserviços | SRP | Cada serviço com uma responsabilidade |
| Clean Architecture | Todos | Separação de camadas e controle de dependências |
| DDD | SRP, DIP | Separação entre domínio e infraestrutura |
| Pipelines de IA/ML | SRP, OCP | Separação de pré-processamento, modelo e pós-processamento |
| Design de API | ISP | Endpoints mínimos necessários |
Em APIs com frameworks web em Python, SOLID aparece em: separar roteamento, regras de negócio e acesso a dados (SRP), adicionar funcionalidades via middlewares (OCP), abstrair o banco para facilitar testes (DIP). Em padrões de segurança em Python, separar validação, autenticação e autorização também é SRP.
Ordem de estudo recomendada
Não é preciso dominar os cinco princípios de uma vez. Uma ordem sugerida:
| Prioridade | Princípio | Motivo |
| 1 | S — Responsabilidade Única | Mais intuitivo e com efeito imediato |
| 2 | O — Aberto/Fechado | Evita explosão de condicionais e ensina extensão segura |
| 3 | D — Inversão de Dependências | Base para design testável; DI é essencial na prática |
| 4 | I — Segregação de Interfaces | Mais útil em projetos maiores |
| 5 | L — Substituição de Liskov | Importante quando herança é usada com frequência |
Comece focando em Responsabilidade Única. Perguntar \u201Ceste classe tem só um motivo para mudar?\u201D já melhora bastante o código. Os outros princípios tendem a fazer mais sentido com a prática.
Equívocos comuns
| Equívoco | Realidade |
| Precisa seguir todos os princípios sempre | O importante é ter consciência; o grau de aplicação varia conforme o contexto |
| Deve ser perfeito desde o início | SRP sozinho já ajuda muito; os outros podem vir aos poucos |
| SOLID elimina bugs | Melhora o design; bugs de lógica são outra questão |
| SOLID é só para OOP | Ideias similares se aplicam em programação funcional |
| Projetos pequenos não precisam | Em projetos pequenos, design pensado no futuro evita problemas depois |
Seguir SOLID de forma rígida pode tornar código simples mais complexo. SOLID é um meio, não um fim. O objetivo é código fácil de mudar e difícil de quebrar; SOLID é um critério para chegar lá.
O que estudar depois de SOLID
| Tema | Relação | Resumo |
| Padrões de design | OCP, DIP | 23 padrões GoF: Strategy, Observer, Factory etc. |
| Clean Architecture | Todos | Arquitetura que estende SOLID |
| Injeção de Dependência | DIP | Padrão concreto para inversão de dependências |
| DDD | SRP, DIP | Design centrado em domínio |
| Refatoração | Todos | Técnicas para tornar código existente mais SOLID |
Perguntas frequentes
Iniciantes devem aprender SOLID?
Sim, quanto antes melhor. Hábitos de design são difíceis de mudar depois. Ter desde cedo a pergunta \u201Cqual o motivo de mudar esta classe?\u201D já faz diferença.
SOLID vale para Python?
Sim. SOLID é design, não linguagem. Vale para Python, Java, C++, TypeScript, Go e qualquer linguagem com OOP. Em Python, por ser dinâmico, o design tende a ser ainda mais importante.
Preciso seguir os cinco princípios?
No início, SRP já basta. Os outros aparecem naturalmente com a experiência.
Quando pensar em SOLID?
Ao desenhar classes e módulos: ao criar uma nova classe, quando uma classe cresce demais ou quando os testes ficam difíceis de escrever.
SOLID é difícil?
Pode parecer abstrato no começo, mas com prática fica natural. SRP e OCP costumam ser os mais fáceis de sentir na pele.
Resumo
Os princípios SOLID são cinco regras de design para código mais resiliente a mudanças, menos propenso a quebrar e mais fácil de estender.
- S (Responsabilidade Única): uma classe, um papel; um motivo para mudar
- O (Aberto/Fechado): estender sem modificar o existente
- L (Substituição de Liskov): subclasses respeitam o contrato da classe pai
- I (Segregação de Interfaces): não forçar funcionalidades desnecessárias
- D (Inversão de Dependências): depender de abstrações, não de implementações
SOLID não é para decorar, é para entender. Saber por que existe ajuda a aplicar no dia a dia. Comece pela Responsabilidade Única e vá evoluindo.
Artigos relacionados: Guia de Introdução ao Python / 7 Padrões de Tratamento de Erros em Python / Comparação de Frameworks Web em Python / 10 Padrões de Segurança em Python / Guia de Comparação de Linguagens de Programação

Leave a Reply