
SRP e DIP com Python
Como aplicar o Princípio da Responsabilidade Única e o Princípio da Inversão de Dependência para construir software mais fácil de manter, compreender e escalar.
Neste artigo, abordo dois conceitos fundamentais para o desenvolvimento de um software que seja fácil de manter, compreender e escalar. Para isso, utilizarei exemplos em Python para demonstrar a aplicação do Princípio da Responsabilidade Única e do Princípio da Inversão de Dependência.
⚠️ Atenção: Para plena compreensão desse artigo é necessário ter conhecimento sobre Programação Orientada a Objetos e sobre sintaxe básica do Python.
🧠 Contextualizando: Para os menos familiarizados com Python, estarei utilizando as seguintes bibliotecas:
- Bcrypt e Hashlib: Para criar e manipular hash de senhas;
- SQLAlchemy: ORM para manipular o banco de dados;
- abc: Para criar e manipular classes abstratas.
O que são esses princípios?
Princípio da Responsabilidade Única (SRP — Single Responsibility Principle)
Esse princípio estabelece que uma classe ou função deve ter apenas uma responsabilidade, ou seja, deve ser especializada em resolver um único problema específico, sem desviar do seu escopo original. Seguir esse princípio torna seu código mais organizado e reduz o impacto de mudanças.
Princípio da Inversão de Dependência (DIP — Dependency Inversion Principle)
Esse princípio defende que as classes não devem depender diretamente de implementações concretas, mas sim de abstrações. Em outras palavras, um código bem estruturado deve receber interfaces ou classes abstratas como dependências, em vez de instâncias específicas. Dessa forma, evita-se um alto acoplamento entre os componentes do sistema, tornando-o mais flexível e fácil de modificar.
Na prática, isso significa que uma classe não precisa saber qual implementação específica está sendo usada — o importante é que essa implementação satisfaça os requisitos esperados pela abstração definida.
Vamos colocar a mão no código!
Nada melhor do que aprender fazendo. Vamos refatorar um código que viola os princípios SRP e DIP e entender os benefícios que essa refatoração trará.
O problema: a classe UserAuthentication
Podemos perceber que essa classe implementa um sistema de autenticação, contendo as funcionalidades básicas de registro e login. No entanto, ao analisarmos seu design, identificamos a seguinte violação do SRP:
Múltiplas responsabilidades:
A classe UserAuthentication está sobrecarregada, pois, além de cuidar da lógica de registro e login, também:
- Manipula diretamente o banco utilizando o SQLAlchemy;
- Realiza a criação e checagem do hash das senhas.
Isso a transforma em uma “God Class” — um antipadrão de software que concentra muitas funções em um único lugar. Esse tipo de design dificulta a manutenção e aumenta a complexidade do código. Qualquer mudança no banco de dados ou na estratégia de hashing exigirá modificações diretas nessa classe, impactando toda a aplicação.
Melhorando o design
Vamos dividir a implementação em três novas classes:
Hashing: Responsável exclusivamente por manipular o hash das senhas, utilizando o algoritmo de sua escolha (no caso, Bcrypt).UserRepository: Responsável somente por manipular a tabela de usuários no banco de dados, usando SQLAlchemy.AuthenticationService: Responsável pela lógica de autenticação, incluindo as funcionalidades de registro e login.
Maravilha! Agora está tudo perfeito, certo?… Não! Ao olhar novamente para o código, identificamos uma violação importante que compromete a flexibilidade do sistema:
Alto Acoplamento e Dependências Fortes
Atualmente, a classe AuthenticationService depende diretamente das instâncias de Hashing e UserRepository. Além disso, a classe UserRepository também depende diretamente de Hashing e do SQLAlchemy. Isso cria um acoplamento forte entre essas classes, tornando o sistema rígido e difícil de modificar.
Se decidirmos mudar qualquer um desses componentes — por exemplo, substituir o banco de dados de SQLite para PostgreSQL, ou trocar o algoritmo de hash de Bcrypt para Argon2 — precisaríamos alterar diretamente essas classes. Isso vai contra a ideia de flexibilidade e reusabilidade do código.
Aplicando o DIP
Para melhorar o desacoplamento e a flexibilidade da aplicação, vamos aplicar o Princípio da Inversão de Dependências criando as seguintes classes abstratas:
AbstractHashingService: Interface que define um contrato para qualquer serviço de hashing. Qualquer implementação concreta (comoBcryptHashingServiceouSHA256HashingService) deverá implementar essa interface.AbstractUserRepository: Interface que define um contrato para o repositório de usuários. As classes que a implementam podem usar diferentes estratégias de acesso ao banco de dados.AbstractDatabaseService: Interface que define um contrato para serviços de banco de dados. Ela abstrai o mecanismo de acesso, permitindo a substituição do banco sem alterar a lógica de autenticação.
Além de criar essas abstrações, implementamos os contratos nas classes concretas.
Conclusão
Conseguimos transformar um código rígido e inflexível em algo muito mais fácil de manter, sem alterar as funcionalidades principais, como registro e login.
Essa é uma excelente oportunidade de reflexão: a funcionalidade não mudou, mas a estrutura do código evoluiu significativamente. Isso nos lembra que, como desenvolvedores, não devemos medir o valor de nosso trabalho apenas pela quantidade de entregas, mas sim pela qualidade do que entregamos.
Criar código sustentável e fácil de manter é, muitas vezes, mais valioso a longo prazo do que apenas adicionar mais funcionalidades.
E você, o que acha? Encontrou algum bug ou tem alguma sugestão de melhoria no código? Deixe seu comentário e vamos continuar essa conversa!