Módulo 15 - Empacotamento e boas práticas

Princípios SOLID em Python

11 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Entender o que cada letra de SOLID representa.
  • Aplicar a responsabilidade única para separar motivos de mudança.
  • Usar o princípio aberto-fechado para estender sem alterar.
  • Reconhecer inversão de dependência com um exemplo em Python.

O que é SOLID

SOLID é um acrônimo para cinco princípios de design que ajudam o código orientado a objetos a envelhecer bem. Eles não são regras rígidas nem uma receita mágica; são diretrizes de bom senso, formuladas para combater os problemas mais comuns de código difícil de mudar. A ideia central por trás de todos é a mesma: reduzir o acoplamento e localizar as mudanças, para que alterar uma parte do sistema não obrigue a mexer em dez outras. Vamos ver cada um com um exemplo simples, sem transformar o assunto em dogma.

LetraPrincípioIdeia central
SResponsabilidade únicaUma classe, um motivo para mudar
OAberto-fechadoEstender sem alterar o que existe
LSubstituição de LiskovSubtipo funciona no lugar do tipo base
ISegregação de interfacesInterfaces pequenas e específicas
DInversão de dependênciaDepender de abstrações, não de detalhes

As cinco letras de SOLID e a ideia central de cada princípio.

Responsabilidade única e aberto-fechado

O primeiro princípio, a responsabilidade única, diz que uma classe deve ter um só motivo para mudar. Uma classe que calcula um pedido, formata o recibo e ainda envia o e-mail tem três motivos para mudar e mistura três assuntos. Separe: uma classe para o cálculo, outra para a formatação, outra para o envio. Assim, mexer no formato do recibo não arrisca quebrar o cálculo. O segundo, o aberto-fechado, diz que o código deve estar aberto para extensão e fechado para modificação: você adiciona um comportamento novo sem editar o código que já funciona e já foi testado.

from abc import ABC, abstractmethod

# Aberto-fechado: para um novo tipo de desconto, crie uma classe nova,
# sem editar as existentes.
class Desconto(ABC):
    @abstractmethod
    def aplicar(self, valor: float) -> float: ...

class SemDesconto(Desconto):
    def aplicar(self, valor: float) -> float:
        return valor

class DescontoPercentual(Desconto):
    def __init__(self, percentual: float):
        self.percentual = percentual
    def aplicar(self, valor: float) -> float:
        return valor * (1 - self.percentual / 100)

def preco_final(valor: float, desconto: Desconto) -> float:
    return desconto.aplicar(valor)

print(preco_final(100, DescontoPercentual(10)))   # 90.0

Um novo desconto é uma classe nova, sem tocar em preco_final: aberto para estender, fechado para modificar.

Veja como os dois princípios se reforçam. A função preco_final não conhece os tipos concretos de desconto; ela só sabe que recebe algo com o método aplicar. Para criar uma promoção nova, como um desconto fixo em reais, basta escrever uma classe nova que herda de Desconto. Nada na função preco_final muda, e o código antigo continua intacto e confiável. Sem esse desenho, você acabaria com uma cadeia crescente de if e elif dentro da função, editada e re-testada a cada promoção nova. O bom design troca a edição arriscada pela extensão segura.

Substituição, segregação e inversão

As três letras finais completam o conjunto. A substituição de Liskov diz que um subtipo deve funcionar em qualquer lugar onde o tipo base é esperado, sem surpresas: se uma função aceita uma Ave e você passa um Pinguim, o programa não pode quebrar só porque pinguim não voa. A segregação de interfaces recomenda interfaces pequenas e específicas, em vez de uma interface gigante que obriga classes a implementar métodos que não usam. E a inversão de dependência diz que o código de alto nível deve depender de abstrações, não de detalhes concretos, o que torna as peças intercambiáveis.

from abc import ABC, abstractmethod

# Inversao de dependencia: o RelatorioService depende da abstracao
# Armazenamento, nao de um banco concreto. Trocar de armazenamento
# nao muda o servico.
class Armazenamento(ABC):
    @abstractmethod
    def salvar(self, texto: str) -> None: ...

class ArmazenamentoArquivo(Armazenamento):
    def salvar(self, texto: str) -> None:
        print(f"salvando em arquivo: {texto}")

class ArmazenamentoMemoria(Armazenamento):
    def __init__(self):
        self.dados = []
    def salvar(self, texto: str) -> None:
        self.dados.append(texto)

class RelatorioService:
    def __init__(self, armazenamento: Armazenamento):
        self.armazenamento = armazenamento
    def gerar(self, conteudo: str) -> None:
        self.armazenamento.salvar(f"RELATORIO: {conteudo}")

# Em producao usa arquivo; no teste, memoria. Mesmo servico.
servico = RelatorioService(ArmazenamentoMemoria())
servico.gerar("vendas do mes")

Inversão de dependência: o serviço recebe a abstração Armazenamento e não conhece o detalhe concreto.

O exemplo da inversão de dependência mostra o poder prático de SOLID. Como o RelatorioService recebe um Armazenamento qualquer, você usa um armazenamento em arquivo na produção e um em memória nos testes, sem tocar no serviço. Essa é a mesma ideia que torna o código testável e flexível. Um alerta importante: SOLID é um guia, não uma obrigação. Aplicar os cinco princípios a um script de dez linhas é exagero que só adiciona complexidade. Use-os quando a flexibilidade compensa a estrutura extra, que é o caso de sistemas que crescem e mudam com frequência.

Teste rápido

O que diz o princípio da responsabilidade única?

Perguntas frequentes

Preciso aplicar SOLID em todo código que escrevo?
Não. SOLID são diretrizes para código que cresce e muda, não regras obrigatórias. Aplicá-las a um script simples só adiciona camadas sem benefício. Use os princípios quando a flexibilidade e a testabilidade que eles trazem realmente compensam a estrutura extra.
SOLID é só para linguagens muito orientadas a objetos?
Os princípios nasceram no mundo orientado a objetos, mas as ideias por trás, separar responsabilidades, depender de abstrações e estender sem modificar, valem em qualquer estilo. Em Python, que mistura objetos e funções, você aplica o espírito de SOLID mesmo sem uma hierarquia rígida de classes.
Qual princípio de SOLID é o mais importante para começar?
A responsabilidade única costuma dar o maior retorno inicial. Separar motivos de mudança já organiza muito o código e prepara o terreno para os outros princípios. Se você aplicar bem só esse, com classes e funções que fazem uma coisa só, já colhe grande parte do benefício.
O que é a inversão de dependência na prática?
É fazer o código de alto nível depender de uma abstração, uma interface, em vez de uma implementação concreta. Assim você injeta a implementação que quiser: um banco real na produção, um dublê em memória nos testes. Isso desacopla as peças e torna o sistema flexível e testável.
SOLID conflita com a simplicidade que o Python valoriza?
Pode conflitar se aplicado em excesso. O Python valoriza soluções simples e diretas, e SOLID mal usado gera abstração demais. A chave é equilíbrio: aplique os princípios onde eles reduzem dor de manutenção real, e prefira o código simples quando a flexibilidade extra não vai ser usada.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.