.NET - Inversão de Controle (IoC) e Injeção de Dependência (DI)


Muito se tem discutido , comentado e escrito sobre padrões de projeto, e, isso é muito bom, significa que estamos evoluindo.

Se você procurar no Google sobre Inversão de controle e Injeção de dependência vai encontrar muita referência e bons artigos com exemplos que procuram mostrar como o que significam como funcionam e como usar estes recursos.

Eu mesmo já escrevi um artigo sobre o padrão inversão de controle (IoC) e neste artigo eu vou dar mais detalhes sobre inversão de controle e injeção de dependência procurando ser bem objetivo e usar exemplos que facilitem o entendimento do assunto.

Apresentando o problema

Vamos iniciar entendendo o problema que esta por trás da inversão de controle, como ele se manifesta e como resolvê-lo.

Observe o trecho de código a seguir onde temos a declaração de uma classe Cliente feita nas linguagens C#  e VB .NET:

O código é bem 'ingênuo'  mas não é difícil encontrar trechos de códigos semelhantes a este não é mesmo ???

namespace InversaoControle
{
    public class Cliente
    {
       Pedido meuPedido = new Pedido();
       public void ObterPedidos(Pedido pedido)
       {
           meuPedido.getPedidos(pedido);
       }
    }
}
Namespace InversaoControle

    Public Class Cliente

     Private meuPedido As  New Pedido()
        Public Sub ObterPedidos(byval pedido As Pedido)
            meuPedido.getPedidos(pedido)
         End Sub
    End Class

End Namespace
Linguagem C# Linguagem VB .NET

Neste código a classe Cliente esta criando uma instância da classe Pedido e definindo o método ObterPedidos() que retorna os pedidos de um cliente usando o método getPedidos definido na classe Pedido.

Este trecho de código não apresenta erros e irá compilar e ser executado sem problema.

Mas então qual o problema com este código ???

Depende !!!

Se você esta apenas brincando ou se você não deseja realizar testes unitários ou ainda se o sistema no qual você esta usando o código é um protótipo ou apenas um programa pessoal para tratar a sua coleção de figurinhas, tudo bem, você não deveria fazer isso, mas você não tem motivos para se preocupar.

Agora, se você é um profissional e um desenvolvedor que trabalha em projetos corporativos ou projetos de médio e grande porte, esta abordagem vai lhe trazer grandes dores de cabeça no futuro.

Se analisarmos sob a ótica do paradigma da orientação a objetos temos logo de início a violação do princípio da responsabilidade única ou SRP - Single Responsability Principle que diz o seguinte:

"Deve existir um e somente UM MOTIVO para que uma classe mude"

Portanto uma classe deve ser implementada tendo apenas um único objetivo.(uma única responsabilidade)

Quando uma classe possui mais que um motivo para ser alterada é por que provavelmente ela esta fazendo mais coisas do que devia, ou seja, ela esta tendo mais de um objetivo.

É o que ocorre com a classe Cliente, nela temos um forte acoplamento com a classe Pedido pois a classe Cliente possui a responsabilidade de criar uma instância da classe Pedido e usar um de seus métodos e isso não deveria ser responsabilidade da classe Cliente.

Dizemos que a classe Cliente esta fortemente acoplada a classe Pedido o que leva a que qualquer mudança feita na classe Pedido afete a classe Cliente.

Com esta abordagem temos os seguintes problemas:

A seguir veremos como diminuir o acoplamento entre as classes Cliente e Pedido.

Resolvendo o problema

Temos um problema e vamos resolvê-lo, não é mesmo ???

Como ???

Fazendo a inversão de controle na classe Cliente e tirando responsabilidades dela.

Ah! Ah! É aqui que entra a inversão de controle...

Vamos inverter o controle na classe Cliente e em vez de deixar a responsabilidade da criação da classe Pedido para a classe Cliente vamos dar a ela esta dependência.

Ah! Ah! É aqui que entra a injeção de dependência...

Vamos injetar a dependência na classe Cliente.

Então para resolver o problema da classe Cliente vamos fazer assim: Inverter o controle utilizando a injeção de dependência.

Percebeu a diferença entre os conceitos ???

O padrão IoC nos diz o que:

"Devemos delegar a tarefa de criação de um objeto (classe Pedido) a uma outra entidade como uma outra classe, interface, componente, etc. de forma a termos um baixo acoplamento e minimizar a dependências entre os objetos."

Aplicando isso ao nosso exemplo temos:

Na figura abaixo temos uma representação deste processo :

A maneira de implementar a inversão de controle chama-se injeção de dependência (DI) que nos trás os seguintes benefícios;

Em suma, a DI isola a implementação de um objeto da construção do objeto do qual ele depende.

Podemos implementar a injeção de dependência das seguintes maneiras:

Implementando a inversão de controle (IoC) usando a injeção de dependência (DI)

1 - Via Construtor

A implementação da injeção de dependência via construtor - Construtor Injection - consiste em passar as dependências de um objeto para o seu construtor.

No nosso exemplo a referência ao objeto deverá ser passado para o construtor da classe Cliente.

Como a classe Cliente depende da classe Pedido temos que passar uma referência de Pedido para o construtor da classe Cliente.

A seguir temos o código desta implementação em VB .NET e C#:

Public Class Cliente
   Private pedido As Pedido
    Sub New(ByVal meuPedido As Pedido)
        pedido = meuPedido	
    End Sub
    Public Sub ObterPedidos(ByVal pedido As Pedido)
        pedido.getPedidos(pedido)
    End Sub
End Class
public class Cliente
{
   private Pedido pedido;

   public Cliente(Pedido meuPedido)
   {
      pedido = meuPedido;
   }

   public void ObterPedidos(Pedido pedido)
   {
      pedido.getPedidos(pedido);
   }
}

Linguagem VB .NET Linguagem C#

Nesta implementação a classe Cliente recebe uma instância da classe Pedido e dessa forma ela não tem mais a responsabilidade de criar uma instância de Pedido.Como a classe Pedido é criada não importa para a classe Cliente ela apenas recebe uma instância dessa classe e utiliza.

Se a classe Pedido for alterada a classe Cliente fique imune a essas alterações pois depende apenas da instância da classe Pedido que foi injetada no seu construtor.

Nesta abordagem temos que estar certos de estar passando uma uma dependência válida para a classe Cliente.(Uma forma de fazer isso será lançar uma exceção no construtor caso a dependência for inválida.) Exemplo:

Sub New(ByVal meuPedido As Pedido)

   If meuPedido Is Nothing Then
        Throw
New ArgumentNullException("meuPedido não pode ser nulo")
   End
If
   pedido = meuPedido

End Sub

Esta abordagem apresenta as seguintes desvantagens:

2 - Via Propriedades

A implementação da injeção de dependência via propriedades - Setter Injection - não força a dependência ser passada para o construtor. Ao invés disso, as dependências são definidas em propriedades públicas expostas pelo objeto.

Esta abordagem tem as seguintes motivações:

A seguir temos o código desta implementação em VB .NET e C#:

Public Class Cliente

  Public Sub New()
  End Sub

  Private meuPedido As IPedido

  Public Property Pedido() As IPedido
   Get
       If meuPedido Is Nothing Then
           Throw New MemberAccessException("meuPedido não foi inicializado")
       End If
     Return meuPedido
    End Get
   Set(value As IPedido)
         meuPedido = value
    End Set
  End Property

End Class
public class Cliente
{
   public Cliente() {}

     private IPedido meuPedido;

    public IPedido Pedido {
    get {
            if (meuPedido == null) {
                 throw new MemberAccessException("meuPedido não foi inicializado");
             }
          return meuPedido;
          }
          set { meuPedido = value; }
     }

}
Linguagem VB .NET Linguagem C#

Esta abordagem apresenta as seguintes vantagens:

Este é o método mais comumente usado para implementar a injeção de dependência. Nele os objetos dependentes são expostos por meio de métodos set/get das classes.

A desvantagem desta abordagem é que os objetos são expostos publicamente e isso quebra a regra de encapsulamento da programação orientada a objeto.

3 - Via Interface

A implementação da injeção de dependência via interface -  Interface Injection -  utiliza uma interface comum que outras classes necessitam implementar para injetar a dependência.

Para este exemplo vou usar o seguinte cenário: Considere um ambiente com duas camadas :

Neste cenário temos a seguinte implementação nas linguagens VB .NET e C#:

Public Interface IBLL
 End
Interface

Public Class ClienteBLL
   
Implements IBLL
End
Class

Public Class ProdutoBLL
    
Implements IBLL
End
Class

Public Class CamadaUI
    
Implements IUI

  Private camadaNegocios As IBLL

  Public Sub SetObjectBLL(camadaNegocios As IBLL)
    
Me.camadaNegocios = camadaNegocios
 
End Sub
End
Class

 interface IBLL
 { }

 class ProdutoBLL : IBLL
 {}

 class ClienteBLL : IBLL
 {}

 class CamadaUI : IUI
 {
   private IBLL camadaNegocios;
   public void SetObjectBLL (IBLL camadaNegocios);
   {
     this.camadaNegocios = camadaNegocios;
   }
 }
VB .NET C#

No código acima temos que o método SetObjectBLL da classe CamadaUI aceita um parâmetro do tipo IBLL.

A seguir temos exemplos de como podemos chamar o método SetObjectBLL para injetar a dependência para qualquer tipo de classe IBLL:

Dim camadaNegocios As IBLL = New ProductoBLL()
Dim camadaUI As CamdaUI = New CamadaUI()
camdaUI.SetObjectBLL(camadaNegocios)

ou

Dim camadaNegocios As IBLL = New ClienteBLL()
Dim camadaUI As CamdaUI = New CamadaUI()
camdaUI.SetObjectBLL(camadaNegocios)
 

IBLL camadaNegocios = new ProductoBLL();
CamdaUI camadaUI = new CamadaUI();
camdaUI.SetObjectBLL(camadaNegocios);

ou

IBLL camadaNegocios = new ProductoBLL();
CamdaUI camadaUI = new CamadaUI();
camdaUI.SetObjectBLL(camadaNegocios);

VB .NET C#

Nesta implementação usando interfaces estamos passando uma referência para um tipo IBLL ao invés de uma instância do tipo.

Vimos assim os principais conceitos sobre inversão de controle e injeção de dependência, suas diferenças e implementações mais usadas.

Em outro artigo mostrarei como implementar a inversão de controle usando o Unity Application Block que é um container bem leve para injeção de dependência que suporta a injeção via construtor, propriedades e chamada de método.

"Passará o céu e a terra, mas as minhas palavras jamais passarão." (Mateus 24:35)

Referências:


Jose Carlos Macoratti