.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) |
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 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 ClienteBLLImplements IBLL End Class Public Class ProdutoBLLImplements IBLL End Class Public Class CamadaUIImplements IUI Private camadaNegocios As IBLLPublic 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() |
IBLL
camadaNegocios = new ProductoBLL(); CamdaUI camadaUI = new CamadaUI(); camdaUI.SetObjectBLL(camadaNegocios); ou IBLL camadaNegocios = new ProductoBLL(); |
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:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#