Injeção de dependência ou Inversão de Controle

Injeção de dependência ou Inversão de Controle Essa é uma confusão comum em muitos devs. O artigo Injeção de dependência ou Inversão de Controle explica SOLID, fala sobre diversos injetores de depenência, explica algumas particularidades que eles possuem. Além disso há exemplos tanto em linguagens de backend quando em frontend. De modo geral é fundamental utilizar para que o acoplamento da aplicação seja controlado, mas deve ser feito corretamente.

Os conhecidos princípios SOLID são essenciais para uma boa construção de código orientado a objetos. Por isso eles auxiliam na construção ao complementar as bases: polimorfismo, encapsulamento etc. Assim, o artigo Injeção de dependência ou Inversão de controle explora esses conceitos, algumas bibliotecas conhecidas e importância do seu uso.

Aqui no blog temos um grande conjunto de artigos relacionados ao design de código. Vamos aqui indicar alguns interessantes. Não há obrigatoriedade em ler tais artigos para compreensão desse, mas talvez complemente se for de seu gosto.

O que é inversão de controle

Inicialmente, o princípio SOLID é uma referência importante para o design de aplicações orientadas a objeto. Desse modo o acrônimo trata de 5 diferentes conceitos de design: (1) responsabilidade única, (2) extensibilidade, (3) estabilidade nas heranças, (4) segregação de interfaces e (5) inversão de controle ou IoC (inversion of control). Então vamos entender melhor esse último.

“Deve-se depender de abstrações, não de objetos concretos.”

Injeção de dependência ou Inversão de Controle?

Esse princípio é muito poderoso e destaca como elementos de código interagem. Assim, a interação direta entre eles pode gerar dependências indesejáveis num código, gerando acoplamento. Então, é até verdade que há acoplamentos desejáveis, mas isso tem que ser visto com cuidado. Por outro lado a inversão de controle dita como relacionar elementos para redução desse acoplamento.

// Interface de repositório do usuário
public interface IUserRepository
{
    void Save(User user);
    User GetById(string userId);
}

// Classe que implementa a interface
public class UserRepository : IUserRepository
{
    public void Save(User user)
    {
        // Implementação para salvar usuário no banco de dados
    }

    public User GetById(string userId)
    {
        // Implementação para buscar usuário no banco de dados por ID
        return null;
    }
}

// Serviço que recebe por parametro do construtor o IUserRepository
// Nesse caso há uma inversão de controle
// Quem toma as ações é o objeto que foi passado por parametro e não a UserService em si
public class UserService
{
    private IUserRepository userRepository;

    public UserService(IUserRepository userRepository)
    {
        this.userRepository = userRepository; // Inversão de controle
    }

    public void SaveUser(User user)
    {
        userRepository.Save(user);
    }

    public User GetUser(string userId)
    {
        return userRepository.GetById(userId);
    }
}

public class User
{
    public string Id { get; set; }
    public string Name { get; set; }
    // Outras propriedades do usuário
}

public class Program
{
    public static void Main()
    {
        IUserRepository userRepository = new UserRepository();
        UserService userService = new UserService(userRepository); // Inversão do controle

        // Utilizar o UserService
        User user = new User { Id = "1", Name = "John Doe" };
        userService.SaveUser(user);
        User retrievedUser = userService.GetUser("1");

        // Restante do código...
    }
}

Qual é a diferença entre inversão de controle e injeção de dependência

A injeção de dependência é a aplicação prática do conceito da inversão de controle que por vezes se dá através de reflexão computacional. Entenda que o uso simples da inversão de controle lida com classes, por exemplo, que fazem parte do mesmo contexto da escrita do software. Mas isso nem sempre é verdadeiro, exemplo: a associação de elementos pode ser feita de modo indireto através de assemblies externos, desde que eles utilizem interfaces padronizadas.

A injeção de dependência pode se dar na prática por diversos modos, como por construtores, anotações, métodos de configuração, etc. que muitas vezes utilizam frameworks de terceiros.

Portanto, a inversão de controle é um padrão de design de sistemas enquanto a injeção de dependência é uma técnica específica que aplica esse design.

Criando manualmente um injetor de dependência

Note que criar um container de injeção de dependência não é algo necessariamente complexo. Então, o exemplo a seguir utiliza a linguagem de programação de C# com Reflection. Desse modo, no exemplo a interface IProduto possui propriedades e o método CalcularDesconto(). Então, o construtor da classe Pedido pega o produtoDLL e o assume dentro dele, criando dinamicamente o objeto concreto. O exemplo é rudimentar mas explica claramente como funciona a essência uma biblioteca de injeção de dependência.

using System;
using System.Reflection;

public interface IProduto
{
    string Nome { get; set; }
    double Preco { get; set; }
    double CalcularDesconto();
}

public class Pedido
{
    private IProduto produto;

    public Pedido(string produtoDLL)
    {
        // Utiliza Reflection para carregar a classe que implementa a interface IProduto em tempo de execução
        Assembly assembly = Assembly.Load(produtoDLL);
        Type tipo = assembly.GetTypes().FirstOrDefault(t => typeof(IProduto).IsAssignableFrom(t));
        produto = Activator.CreateInstance(tipo) as IProduto;
    }

    public void AdicionarProduto(string nome, double preco)
    {
        produto.Nome = nome;
        produto.Preco = preco;
    }

    public void ImprimirPedido()
    {
        Console.WriteLine($"Produto: {produto.Nome} - Preço: {produto.Preco}");
        Console.WriteLine($"Desconto: {produto.CalcularDesconto()}");
    }
}

Injeção de dependência ou Inversão de Controle em JS / TS

O código a seguir foi criado utilizando ES6+ e um framework específico chamado InversifyJS.

// Importando as dependências necessárias do InversifyJS
import { injectable, inject } from 'inversify';

// Definindo a classe UserRepository
@injectable()
class UserRepository {
  // Implementação do UserRepository
}

// Definindo a classe UserService
@injectable()
class UserService {
  private userRepository: UserRepository;

  // Injetando a dependência do UserRepository
  constructor(@inject(UserRepository) userRepository: UserRepository) {
    this.userRepository = userRepository;
  }

  // Outros métodos da classe UserService que utilizam o UserRepository
}

import { Container } from 'inversify';

// Criando um contêiner do InversifyJS
const container = new Container();

// Registre as classes UserRepository e UserService no contêiner
container.bind<UserRepository>(UserRepository).toSelf();
container.bind<UserService>(UserService).toSelf();

// Resolvendo a instância do UserService com todas as suas dependências injetadas
const userService = container.resolve<UserService>(UserService);

Injeção de dependência ou Inversão de Controle em C#

Há um grande conjunto de frameworks que auxiliam na implementação da inversão de controle. Assim, alguns oferecem uma estrutura de configuração melhor, outros suportam singleton e coisas do tipo, outros suportam Orientação a aspecto, etc. Porém, existem particularidades que precisam ser analisadas. Pessoalmente trabalhei muito com o Ninject no .NET Full Framework e no .NET Core utilizo o nativo da Microsoft. Veja a seguir uma lista com várias frameworks diferentes.

  • Autofac: flexível e extensível com suporte a recursos avançados, como escopos, interceptação e módulos.
  • Unity: leve e fácil de usar, desenvolvido pela Microsoft, com suporte a configuração por meio de anotações ou arquivos de configuração.
  • Ninject: ágil e poderoso, com uma sintaxe amigável e suporte a várias estratégias de resolução de dependência.
  • Castle Windsor: maduro e completo, com suporte a recursos avançados, como interceptação e instalação de componentes por meio de configuração XML ou código.
  • Simple Injector: rápido e fácil de usar, projetado para desempenho e verificação em tempo de compilação.
  • Microsoft.Extensions.DependencyInjection: Um injetor de dependência fornecido pela Microsoft como parte do pacote Microsoft.Extensions.DependencyInjection, amplamente utilizado em aplicativos ASP.NET Core.
  • StructureMap: flexível e configurável, com suporte a escopos, interceptação e extensibilidade.
  • Lamar: rápido e flexível, construído em cima do StructureMap, com suporte a recursos avançados e configuração fluente.
  • DryIoc: extremamente rápido, com suporte a recursos avançados, como resolução de dependência automática e instalação de componentes por meio de configuração fluente.
  • LightInject: Um injetor de dependência leve e de alto desempenho, com uma sintaxe simples e suporte a recursos avançados, como interceptação e configuração fluente.

Injeção nativa do ASP.NET Core

A Microsoft oferece uma estrutura nativa para injeção de dependência através do Package Microsoft.Extensions.DependencyInjection. Isso torna as coisas um pouco mais naturais no dia a dia. Vale destacar que há três diferentes formas de realizar a injeção, como pode ser vista no exemplo abaixo;

public void ConfigureServices(IServiceCollection services)
{
   services.AddScoped<IScopedService, ScopedService>();
   services.AddSingleton<ISingletonService, SingletonService>();
   services.AddTransient<ITransientService, TransientService>();
}

Add Scoped

Esse é o formato mais utilizado. Gera-se toda vez uma classe concreta por estar relacionada a uma abstração.

Add Singleton

O Add Singleton é o contrário do Add Scoped, gera-se apenas uma vez para todas as sessões porventura o utilize.

Add Transient

Já o Add Transient é um misto dos dois modelos, funcionando como singleton, gerando uma instância, mas sem compartilhamento. Ou seja, nesse caso, essa instância é válida apenas para a sessão corrente.

Conclusão de Injeção de dependência ou Inversão de Controle

Essa é uma confusão comum em muitos devs. O artigo Injeção de dependência ou Inversão de Controle explica SOLID, fala sobre diversos injetores de depenência e explica algumas particularidades que eles possuem. Além disso há exemplos tanto em linguagens de backend quando em frontend. De modo geral utilize injeção de dependência para reduzir o acoplamento. Mas, faça corretamente!


Thiago Anselme
Thiago Anselme - Gerente de TI - Arquiteto de Soluções

Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.

Deixe um comentário