Dev (Back & Front)ARTIGO

Você está confundindo Injeção de Dependência com Inversão de Dependência? Vou te ajudar a entender!

Você está confundindo Injeção de Dependência com Inversão de Dependência? Vou te ajudar a entender!
Imagem: Cláudio Raposo

Em nosso artigo anterior, refatoramos um sistema de registro de usuários utilizando o Princípio da Responsabilidade Única (SRP) . Também utilizamos Injeção de Dependência para passar nosso repositório e validador para o serviço.

Motivos pelos quais seu código Python é difícil de testar com o Princípio da Responsabilidade Única e como corrigir isso!

O Princípio da Responsabilidade Única (SRP, na sigla em inglês) é a base dos princípios SOLID. Ele afirma que uma classe deve ter…

engcfraposo.medium.com

No entanto, se você observar atentamente nosso código anterior, ainda existe um acoplamento oculto. Ele UserServicedepende diretamente da classe concreta UserRepository.

Isso nos leva ao Princípio da Inversão de Dependência (DIP) — o “D” em SOLID.

A Grande Confusão: Injeção vs. Inversão

Antes de refatorarmos, precisamos esclarecer um ponto de confusão muito comum: qual a diferença entre Injeção de Dependência e Inversão de Dependência?

1. Injeção de Dependência (O “Como”)

Injeção de Dependência é uma técnica . Significa simplesmente: “Não crie suas dependências dentro da sua classe; solicite-as no construtor.”

  • Sem injeção de dependência: UserService cria UserRepositoryinternamente.
  • Com DI: UserService recebe UserRepositorycomo parâmetro.

2. Princípio da Inversão de Dependência (A “Regra”)

A inversão de dependência é um princípio arquitetural . Ela afirma: “Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.”

Em nosso código anterior, usamos Injeção, mas violamos a Inversão.

  • A violação: UserService (Nível Alto) importa e utiliza UserRepository(Nível Baixo).
  • A solução: UserService deve depender de um UserRepositoryInterface. O tambémSQLUserRepository deve depender dessa interface.

❌ O “Antes”: Ligado aos detalhes

Vamos analisar o __init__método do nosso exemplo anterior de SRP.

class  UserService : 
    # PROBLEMA: Estamos usando type hinting na classe CONCRETA. 
    # Estamos dizendo: "Eu preciso especificamente desta classe UserRepository exata." 
    def  __init__ ( self , validator:  UserValidator , repository:  UserRepository ): 
        self.validator = validator 
        self.repository = repository

Se quisermos trocar UserRepository(o que salva no SQLSQL64 conteúdosSQL Server – Como evitar SQL Injection?Data · mai 2019Azure SQL DB Managed InstanceData · abr 2019SQL Server – Como evitar SQL Injection? Pare de utilizar Query Dinâmica como EXEC(@Query)Data · abr 2019Ver tudo em Data ) por um FakeUserRepository(para teste) ou um MongoUserRepository, teoricamente violamos o contrato de tipo definido aqui. O UserServicesabe demais sobre os detalhes de implementação de UserRepository.

✅ A Refatoração: Introduzindo Abstrações

Para aderir ao DIP em PythonPython56 conteúdosVSCode + Python + Alexa: Desenvolva e teste skills para alexa localmente com pythonDev (Back & Front) · out 2025Dominando decoradores em Python: um guia completo com exemplosDev (Back & Front) · jan 2025Desenvolvimento de software: diferenças entre Python, JavaScript e JavaGestão Dev & TI · nov 2024Ver tudo em Dev (Back & Front) , usamos Classes Base Abstratas (ABCs) para definir “Interfaces”.

Etapa 1: Definir as interfaces (O contrato)

Criamos classes abstratas que definem o que precisa ser feito, sem definir como .

from abc import ABC, abstractmethod 

# O contrato para armazenamento 
class  UserRepositoryInterface ( ABC ): 
    @abstractmethod 
    def  exists ( self, email: str ) -> bool : 
        pass 
    @abstractmethod 
    def  save ( self, user: 'User' ): 
        pass 

# O contrato para validação 
class  UserValidatorInterface ( ABC ): 
    @abstractmethod 
    def  validate ( self, user: 'User' ): 
        pass

Etapa 2: Implementar as classes concretas

Agora, nossas classes de baixo nível implementam essas interfaces.

# Módulo de baixo nível que implementa a interface 
class  SQLUserRepository ( UserRepositoryInterface ): 
    def  exists ( self, email: str ) -> bool : 
        # Simula verificação SQL 
        return email == "taken@example.com" 
    def  save ( self, user: 'User' ): 
        # Simula salvamento SQL 
        print ( f"[SQL] INSERT INTO users VALUES (' {user.name} ', ' {user.email} ')" ) 

# Outro módulo de baixo nível (por exemplo, para testes ou banco de dados diferente) 
class  InMemoryUserRepository ( UserRepositoryInterface ): 
    def  __init__ ( self ): 
        self .db = [] 
    def  exists ( self, email: str ) -> bool : 
        return  any (u.email == email for u in  self .db) 
    def  save ( self, user: 'User' ): 
        self .db.append(user) 
        print ( f"[MEMORY] Salvo {user.name} na lista." ) 

class  StandardUserValidator ( UserValidatorInterface ): 
    def  validate ( self, user: 'User' ): 
        if  len (user.name) < 3 : 
            raise ValueError( "Nome muito curto" ) 
        if  "@"  not  in user.email: 
            raise ValueError( "Email inválido" ) 
        if  len (user.password) < 6 : 
            raise ValueError( "Senha muito fraca" )

Etapa 3: Refatorar o serviço (o módulo de alto nível)

Eis a mágica. O UserServicejá não sabe que aquilo SQLUserRepositoryexiste. Só sabe sobre aquilo outro UserRepositoryInterface.

class  UserService : 
    # DIP APLICADO: Dependemos da Interface (Abstração), não da classe concreta. 
    def  __init__ ( self, validator: UserValidatorInterface, repository: UserRepositoryInterface ): 
        self.validator = validator 
        self.repository = repository 
    def  register ( self, name, email, password ): 
        user = User(name, email, password) 
        self.validator.validate (user) 
        if  self.repository.exists (email): 
            raise ValueError( "Email já está em uso" ) 
        self.repository.save (user) 
        print ( ">">> Usuário cadastrado com sucesso." )

Colocando tudo em prática

Agora podemos trocar implementações facilmente sem alterar uma única linha de código em UserService.

    # Cenário A: Produção (Usando SQL)
     sql_repo = SQLUserRepository() 
    std_validator = StandardUserValidator() 

    # O serviço funciona com SQL
     service_prod = UserService(std_validator, sql_repo) 
    service_prod.register( "Alice" , "alice@sql.com" , "secure123" ) 
    print ( "-" * 30 ) 

    # Cenário B: Teste (Usando Memória) 
    # Não alteramos o código do UserService, mas mudamos completamente o comportamento.
     memory_repo = InMemoryUserRepository() 

    # O MESMO serviço funciona com memória
     service_test = UserService(std_validator, memory_repo) 
    service_test.register( "Bob" , "bob@memory.com" , "secure123" )

Resumo

Ao aplicar o Princípio da Inversão de Dependência, invertemos o fluxo de controle:

  • Antes: UserService -> dependia de ->SQLUserRepository
  • Depois: UserService -> depende de -> Interface<- implementado por <-SQLUserRepository

Por que isso importa?

  • Acoplamento fraco: a lógica de alto nível (fluxo de registro) é protegida dos detalhes de baixo nível (SQL, MongoDB, sistema de arquivos).
  • Flexibilidade: Você pode alternar entre provedores de banco de dados simplesmente passando uma instância de classe diferente.
  • Testes: Você pode facilmente passar um MockRepositoryobjeto que implementa a interface, tornando os testes unitários rápidos e confiáveis.

Systems Architect Project Leader @NTT DATA👨‍💻 | 2y Microsoft MVP® Developer Technologies 🏆 | 1y Microsoft MVP Azure® 🏆 | 2y Docker Captain® 🏆 | Undergraduate Teacher @Moveedu 👨‍🏫 | 🇺🇸 DBA, Hon.D.Sc., MCS, MBA

Ver perfil