Campo de busca

Categoria Programação

Inversão de Controle (IoC) e Injeção de Dependência (DI) no C#: desacoplando sua aplicação e facilitando seus testes

Inversão de Controle (IoC) e Injeção de Dependência (DI) no C#: desacoplando sua aplicação e facilitando seus testes

Por | 30 de agosto, 2013 | 12 comentários

Quando vamos desenvolver uma aplicação, não basta simplesmente sair escrevendo o código sem antes persarmos em sua arquitetura. Existem preocupações que devemos considerar sobre o software, como por exemplo, sua testabilidade, extensibilidade, manutenibilidade, etc. Isso tudo se torna muito difícil quando temos um alto acoplamento entre as classes. E é aí que entra a Inversão de Controle e a Injeção de Dependência. Hoje vou explicar o que são esses conceitos e como implementar em C#.

Inversão de Controle

Entendendo o problema

Para começar, precisamos entender o problema resolvido pela Inversão de Controle (Inversion of Control ou IoC). Vamos imaginar a seguinte situação: um controller Pedidos que utiliza o repositório de pedidos para listar os pedidos do banco de dados, por exemplo. Veja:

public class PedidosController : Controller
{
    //
    // GET: /pedidos/

    public ActionResult Index()
    {
        var pedidoRepositorio = new PedidoRepository();
           
        var pedidos = pedidoRepositorio.ObterTodos();

        return View(pedidos);
    }

}

O problema com o código acima é o alto acoplamento. A classe PedidosController é responsável por instanciar o repositório, por isso dizemos que ela tem dependência da classe PedidoRepository. Nesse caso, qualquer alteração no repositório de pedidos, vai afetar diretamente o controller. O repositório, por sua vez, utiliza uma conexão ao banco de dados para obter os produtos, então seu controller também está dependente disso! Você pode pensar que nunca irá mudar essa conexão, mas e seus testes unitários? Eles devem independer de acesso ao banco de dados, mas utilizando o código acima, ao testar seu controller de pedidos, ele irá instanciar a classe repositorio que vai precisar da conexão.

Se você desenvolve com TDD, já deve ter percebido o problema. Para testar o controller acima, não conseguimos "mockar" o repositório!

Como resolvemos esse problema?

Através da Inversão de Controle. Isso quer dizer que vamos inverter o controle na classe PedidosController, tirando a responsabilidade dela sobre a classe PedidoRepository e passando essa responsabilidade para outra classe, interface, componente, etc.

Com isso, chegamos aos princípios do S.O.L.I.D, que define em sua letra D o princípio de Inversão de Dependência (Dependency Inversion):

Módulos de alto nível não devem depender de módulos de baixo nível, ambos devem depender de abstrações.

Uma das formas de fazer a Inversão de Controle seguindo os princípios do S.O.L.I.D, é a Injeção de dependência.

Injeção de Dependência

A Injeção de Dependência (Dependency Injection ou DI) é um design pattern para realizar a Inversão de Controle. Como já vimos acima, se a sua classe depende de outra classe, então devemos criar a dependência para uma classe abstrata. Com a sua classe dependendo de uma abstração, devemos injetar um objeto concreto nela.

De acordo com Martin Fowler em seu artigo sobre esse assunto, a Injeção de Dependência não é a única maneira de remover dependências entre as classes. Existe também o padrão Service Locator, porém não tratarei dele nesse post.

Injetando uma dependência

Existem três maneiras de injeção de dependência:

Construtor (Constructor Injection): as dependências do objeto são injetadas diretamente em seu construtor.
Propriedade (Setter Injection): dependências do objeto são injetadas via setter em alguma(s) propriedade(s).
Interface: o objeto a ser injetado é uma abstração da classe concreta. (na forma interface ou mesmo classe abstrata)

Vamos alterar nosso código inicial do controller de Pedidos utilizando o Constructor Injection. Criamos uma interface para o repositório de pedidos, IPedidoRepository, que o controller receberá como parâmetro no construtor.

public class PedidosController : Controller
{
    private IPedidoRepository _pedidoRepositorio;

    public PedidosController(IPedidoRepository pedidoRepositorio)
    {
        _pedidoRepositorio = pedidoRepositorio;
    }

    //
    // GET: /pedidos/

    public ActionResult Index()
    {
        var pedidos = _pedidoRepositorio.ObterTodos();

        return View(pedidos);
    }

}

Dessa forma, PedidosController deverá ter a classe de repositório injetada no seu construtor. Com isso temos uma Inversão de Controle e estamos respeitando o princípio do S.O.L.I.D, pois a classe está dependendo apenas da abstração da outra classe.

O código acima é compilado sem nenhum erro, porém ao executarmos esse controller ocorrerá um erro, já que não temos um construtor sem parâmetros definido e nenhuma interface sendo injetada na classe. Para resolvermos esse problema vamos utilizar um terceiro elemento para injetar a dependência do controller, nesse caso utilizaremos o Ninject.

Ninject

O Ninject é um framework de injeção de dependência para aplicações .NET. O Ninject possui uma extensão para integração com o ASP.NET MVC, o Ninject.MVC3, que facilitará nosso trabalho. Você pode baixá-lo pelo próprio NuGet através do comando:

install-package Ninject.MVC3

Após instalar o Ninject, observe que o arquivo NinjectWebCommom.cs foi adicionado na pasta App_Start. As configurações do Ninject já estão prontas para o MVC, portanto só precisamos registrar as dependências dentro do método RegisterServices da seguinte maneira:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IPedidoRepository>().To<PedidoRepository>();
}

E pronto! Nem precisamos criar uma classe que implementa a interface IDependencyResolver, pois o Ninject.MVC3 já fez todo esse trabalho. Agora quando rodarmos novamente a aplicação, não apresentará mais erro, pois será injetado uma classe concreta no controller.

Agora fica muito fácil "mockar" o repositório, pois podemos passar o mock da interface IPedidoRepository para o construtor do controller.

Conclusão

Nesse post vimos os conceitos de Inversão de Controle e Injeção de Dependência, entendendo os problemas que eles resolvem e como implementar em C#. Utilizamos o Ninject.MVC3 para facilitar nosso trabalho. Os benefícios de utilizar esse padrão com certeza ficarão cada vez mais evidentes conforme sua aplicação aumenta de proporção e vai se tornando mais complexa. Pode ser que você gaste um pouquinho mais de tempo para implementar a injeção de dependência, mas com certeza ganhará mais tempo na testabilidade e manutenibilidade da aplicação.

Referências

 

Deixe um comentário

Queremos saber o que você pensa sobre esse post! Ficaremos felizes se você nos deixar um comentário :-)

Tenha em mente que os comentários são moderados, e os links possuem rel="nofollow". Então por favor, não faça spam de palavras-chave, senão o seu comentário será excluído. Obrigado!

 

[12] Comentários  

Adhan Maldonado

Oi Jéssica,
Sobre o artigo, a partir do momento que eu registro a dependencia dentro da classe RegisterServices, como que faço a chamada na minha controller? A chamada é feita da mesma forma que vc fez? A diferença é que com o Ninject ao ser chamada essa controller ela nao apresentará erro pela ausência do contrutor vazio?
Grato

27-09-2013 às 17:53 Responder

Jéssica Schissato

Jéssica Schissato

Em resposta a Adhan Maldonado

Olá, Adhan!

Exatamente, você mesmo já respondeu sua dúvida. Não será necessário fazer nenhuma alteração no controller, pois ao registrar a dependência no método RegisterServices, não apresentará mais erro pela ausência do construtor vazio, já que o Ninject será responsável por injetar a classe no controller. =)

30-09-2013 às 13:49 Responder

Raphael

Excelente artigo. Simples, claro e direto. DI é algo extremamente necessário e as vezes "travamos" na hora de explicar e até mesmo colocar em prática.
Você abordou ele de tal forma que fez parecer fácil, da forma que realmente é!
Parabéns!

27-11-2013 às 12:52 Responder

Rafael

Esse artigo ajuda muito! Além de estar muito bem explicado, parabéns!!

16-12-2013 às 13:55 Responder

Rodrigo

Em resposta a Rafael

IOC e DI fazem parte dos princípios aplicados a SOLID.
Em alguns dos meus trabalhos utilizei frameworks como o structuremap e também o ninject que apresenta seu núcleo de registro de fácil entendimento.
Ótimas ferramentas para usar e aplicar com testes dubles sobre moq.

29-12-2013 às 16:34 Responder

Cassiano Peixoto

Parabéns pelo artigo! :) Só um questionamento. O "I" do SOLID não significa Interface Segregation?

14-05-2014 às 10:05 Responder

Jéssica Schissato

Jéssica Schissato

Em resposta a Cassiano Peixoto

Obrigada, Cassiano!

Isso mesmo, a letra I do S.O.L.I.D significa Interface segregation, porém não entrei em detalhes sobre cada princípio, somente sobre letra D (Dependency inversion), que tem relação com o assunto do post.

14-05-2014 às 10:40 Responder

andrey

Ola. Criei um projeto para estudar injecao de dependencia. Ele possui camadas de acesso a dados, regra de negocios, interface com o usuario, alem da camada de servicos (wcf, web api). Neste cenario me deparei com a seguinte situacao: em acesso a dados e regra de negocio tenho sempre a relacao de uma interface para uma classe (ex: IUsuarioDAL e UsuarioDAL. IUsuarioBLL e UsuarioBLL). Para realizar a injecao criei uma camada para o proposito, onde utilizo Unity. Esta camada e "CHAMADA" nas camadas mais altas. Ate o momento tudo blz. A minha duvida eh a seguinte, e se eu possuir uma interface para varias classes? Toda a logica q criei ficaria com um "furo". Pois se a camada de servico nao precisar da classe UsuarioBLL e sim de UsuarioServiceBLL, por exemplo (estas classes implementam IUsuarioBLL). Sera q precisarei de um container Unity para cada camada de alto nivel? Se voce tiver um exemplo de projeto "maduro" com relacao a injecao de dependencia por deria me falar a logica dele? Desde ja agradeco...

02-08-2014 às 11:31 Responder

Jéssica Schissato

Jéssica Schissato

Em resposta a andrey

Olá, Andrey!

Nesse caso você teria sim que criar uma interface IUsuarioServiceBLL para o UsuarioServiceBLL, uma vez que ela tem um propósito diferente da UsuarioBLL. Mesma que em sua aplicação os métodos estejam com assinaturas parecidas ou idênticas, as classes possuem objetivos diferentes e devem ter interfaces diferentes, até porque sua classe de serviço poderá ter métodos e retornos de métodos diferentes, por exemplo se você está utilizando Message Patterns em sua camada de serviços.

Você pode utilizar a mesma interface em duas classes caso elas tenham implementações diferentes para esta interface, por exemplo uma interface para as configurações da aplicação IApplicationSettings, pode ter duas implementações, uma que retorna as configurações do WebConfig e outra implementação que retorna as configurações de um arquivo, que seriam respectivamente: WebConfigApplicationSettings e FileApplicationSettings, ambas implementando a mesma interface. Nesse caso sim elas precisam da mesma interface, pois você irá controlar na sua injeção de dependência qual das duas será utilizada, sem precisar trocar a interface e os métodos ou parâmetros utilizados.

11-08-2014 às 09:33 Responder

andrey

Em resposta a Jéssica Schissato

olá, bom exemplo: IApplicationSettings, WebConfigApplicationSettings e FileApplicationSettings.

neste caso para projetos que teriam que utilizar webConfigApplicationSetings, eu teria uma camada de DI especifica para este projeto? você aconselharia uma camada de DI generica e outra mais especifica, caso necessário, para evitar duplicação de registers em containers diferentes?

13-08-2014 às 07:45 Responder

Pedro Ávila

Hola
Como puedo implentar ninject para la conexión estoy usando NHibernate, aplicando SOID dice que un método o clase debe tener una única responsabilidad y mis métodos tiene 2 uno abren la conexión y dos la acción de un Insert, update, delete, etc.

Quiero solucionarlo aplicando ninject

27-08-2014 às 20:31 Responder

Matheus

Parabéns!
Didático e de fácil entendimento, o Ninject mudou minha vida a algum tempo portanto sempre recomendo posts como o seu.

04-08-2016 às 15:36 Responder