Implementar Unity - Injeção de Dependência (Dependency Injection) C#

watch_later 28 de jun de 2013
Introdução
A intenção deste post é para explicar injeção de dependência usando Unity, e em particular a abordagem singleton (que é muito estática na sua abordagem, mas tem um lugar em um padrão de design - por exemplo, para uma classe logger, que não precisa ser re-instanciado em cada classe que usa-lo).

O que é Injeção de Dependência?

Injeção de dependência também é conhecido como inversão de controle (IOC). É um padrão de design que remove o forte acoplamento entre os componentes dependentes. Ela facilita a concepção e implementação de baixo acoplamento, objetos reutilizáveis​​, e testáveis ​​em seu software.

Quando devo usar o Unity Dependency Injection?

Injeção de dependência oferece oportunidades para simplificar o código, as dependências entre objetos abstratos e gerar automaticamente instâncias de objetos dependentes. Em geral, você deve usar o Unity quando:

- Seus objetos e classes podem ter dependências em outros objetos ou classes.
- Suas dependências requerem abstração.
- Você quer tirar proveito dos recursos de injeção de construtor.
- Você quer gerenciar a vida de instâncias de objetos.

- Você quer interceptar chamadas de métodos ou propriedades para gerar uma corrente política ou canalizar os manipuladores que implementam tarefas transversais.

Benefícios de usar o Unity

Unity fornece aos desenvolvedores as seguintes vantagens:

- Ele fornece a criação de objetos simplificados, especialmente para estruturas de objetos hierárquicos e dependências, o que simplifica o código do aplicativo. Ele contém um mecanismo de construção (ou de montagem) instâncias de objectos, que pode conter outras ocorrências de objetos dependentes.

- Ele suporta abstração de requisitos, o que permite que aos desenvolvedores especificarem dependências em tempo de execução ou na configuração e simplificar a gestão dos interesses transversais.

- Ele aumenta a flexibilidade, adiando configuração do componente ao container. Ela também suporta uma hierarquia para os contentores.

- Ele tem uma capacidade local de serviço, o que é útil em muitas situações em que uma aplicação faz uso repetido de componentes que separam e centralizam funcionalidades.

- Ele permite aos clientes armazenar em cache ou containers Isto é especialmente útil em aplicações Web ASP.NET onde os desenvolvedores podem manter os containers na sessão ou aplicação ASP.NET.

- Ele tem uma capacidade de interceptação, que permite aos desenvolvedores adicionar funcionalidade para componentes existentes, criando e utilizando manipuladores que são executados antes de um método ou propriedade chamada atinge o componente de destino e, novamente, como as chamadas de retorno.

- Ele pode ler as informações de configuração de sistemas de configuração padrão, como arquivos XML, e usá-lo para configurar os containers.

- Ele não faz exigências sobre a definição de classe do objeto. Não há nenhuma exigência para aplicar atributos a classes (exceto quando usamos as propriedades ou chamada de injeção método), e não há limitações na declaração da classe.

- Ele suporta extensões de contêineres personalizados que você pode implementar, por exemplo, você pode implementar métodos para permitir a construção objeto adicional e recursos de recipiente, como caching.


- Ele permite que os arquitetos e desenvolvedores para implementar mais facilmente padrões de design comuns frequentemente encontrados em aplicações modernas.

Quais tipos de objetos Unity é possível criar?

Você pode usar o container Unity para gerar instâncias de qualquer objeto que tem um construtor público (em outras palavras, os objetos que você pode criar usando o novo operador), sem registrar um mapeamento desse tipo com o container. Quando você chamar o método Resolve e especificar a instância padrão de um tipo que não está registrado, o container implesmente chama o construtor para esse tipo e retorna o seu resultado.

Gerenciando a vida útil de objetos

Unity permite que você escolha o tempo de vida de objetos que ele cria. Por padrão, o Unity cria uma nova instância de um tipo cada vez que você resolver esse tipo. No entanto, você pode usar um gerenciador de vida para especificar uma vida diferente para certos objetos que você escolher. Por exemplo, você pode especificar que a Unity deve manter apenas uma única instância (efetivamente, um singleton). Ele vai criar uma nova instância somente se não houver nenhuma instância existente. Se não houver uma instância existente, ele irá retornar uma referência a este lugar. Além disso, você pode usar um gerenciador de vida que mantém apenas uma referência fraca para objetos de modo que o processo de criação pode dispor deles, ou um gerenciador de vida que mantém uma única instância separada de um objeto em cada segmento separado que resolva isso.

Explicação de uso

Registrar as interfaces com Unity

Na linha abaixo, criamos uma instância da UnityContainer.

new UnityContainer();

O primeiro tipo de inscrição (trecho abaixo) que criamos é a classe "Employee" Essa classe usa a classe "Person" como um parâmetro de construtor. Nós estamos dando o registro de um nome de Person-non-singleton ', que mais tarde pode fazer referência a esta classe "Employee" específico. A classe "Person", neste caso, será criado de novo cada vez que (não uma vida singleton).

// Employee class with Person (non-singleton) constructor parameter      
myContainer.RegisterType<IEmployee, Employee>("Person-Non-Singleton");  
myContainer.Resolve<IEmployee>("Person-Non-Singleton");

O próximo passo, registrar a classe "Employee" para tirar um parâmetro mapeada (neste caso, o nome do mapeamento é "Person-Singleton" de uma classe singleton "Person". Os nomes de mapeamento são utilizados no nosso código para fazer referência a um tipo específico registada, como se pode ter a classe "Employee" gerada num certo número de maneiras diferentes.

// Employee class with Person (singleton) constructor parameter

myContainer.RegisterType<IEmployee, Employee>("EmployeeWithPersonSingleton", 
new InjectionConstructor(new ResolvedParameter<Person>("Person-Singleton")));
myContainer.Resolve<IEmployee>("EmployeeWithPersonSingleton");

O código a seguir mostra o registro da classe "Person" que será usado na criação do padrão "Employee". Nesse, a classe "Person" não é um singleton e não detém quaisquer valores que possam ter sido atribuídas a classe singleton uma vida anterior "Person".

// Person (non-singleton)  

myContainer.RegisterType<IPerson, Person>("Person-Non-Singleton");
myContainer.Resolve<IPerson>("Person-Non-Singleton");

E, finalmente, um registro da classe "Person", mas como um exemplo de vida singleton, assim quando ele é referenciado novamente que irá realizar todos os valores anteriores que foram atribuídas a ele anteriormente.

// Person (singleton)        
myContainer.RegisterType<IPerson, Person>
 ("Person-Singleton", new ExternallyControlledLifetimeManager());        
myContainer.Resolve<IPerson>("Person-Singleton");
try
      {
        myContainer = new UnityContainer();
        // Employee class with Person (non-singleton) constructor parameter        
        myContainer.RegisterType<IEmployee, Employee>("Person-Non-Singleton");       

        myContainer.Resolve<IEmployee>("Person-Non-Singleton");
        // Employee class with Person (singleton) constructor parameter
        myContainer.RegisterType<IEmployee, Employee>
 ("EmployeeWithPersonSingleton", new InjectionConstructor
 (new ResolvedParameter<Person>("Person-Singleton")));
        myContainer.Resolve<IEmployee>("EmployeeWithPersonSingleton");
        // Person (singleton)        
        myContainer.RegisterType<IPerson, Person>
 ("Person-Singleton", new ExternallyControlledLifetimeManager());        
        myContainer.Resolve<IPerson>("Person-Singleton");
        // Person (non-singleton)  
        myContainer.RegisterType<IPerson, Person>("Person-Non-Singleton");
        myContainer.Resolve<IPerson>("Person-Non-Singleton");
        outMessage = "Populated Unity Container from the Application object.\n";
      }
      catch (Exception ex)
      {
        outMessage = "Error: Unity Container not populated correctly. 
   \n Details: " + ex.Message + "\n";
      }

Eventos On-Click

private void btnNewEmployee_Click(object sender, RoutedEventArgs e){
      IEmployee myEmployeeInstance = myContainer.Resolve<IEmployee>
 ("Person-Non-Singleton");  // resolve an instance of the class 
    // registered for IEmployee
      this.txtblkOutput.Text += myEmployeeInstance.myPerson.Print(); // display the 
     // result of calling the class method  
    }

Quando o usuário clica no botão "New classe Employee w \ non-singleton classe Person ", uma nova classe empregado é criado com uma classe não-singleton (mapeamento) "Person". Se você imprimir a idade da pessoa que vai ser sempre 0, pois vai ser uma classe "Person" recém instanciado.

O seguinte trecho de código irá imprimir o "Person" propriedade idade (que se não for atualizada será 0), mas se você atualizar o valor e, em seguida, clique neste botão novamente, ele irá exibir a idade da pessoa atualizada (mesmo que nós criamos uma nova instância da classe "Employee " - acoplamento fraco.

private void btnNewEmployeeSingleton_Click(object sender, RoutedEventArgs e)
    {
      IEmployee myEmployeeInstance = 
 myContainer.Resolve<IEmployee>("EmployeeWithPersonSingleton");      
      this.txtblkOutput.Text += myEmployeeInstance.myPerson.Print();     
    }

Este método irá fazer o mesmo que o método acima, mas é só para provar o ponto de que uma nova instância de "Employee " é criada, mas com um gerenciador de vida singleton para a classe "Person" (assim você vai ver a idade atualizada propriedade que está sendo exibido).

private void btnCurrentEmployeeSingleton_Click(object sender, RoutedEventArgs e)
    {
      IEmployee myEmployeeInstance = myContainer.Resolve<IEmployee>
     ("EmployeeWithPersonSingleton");     
      this.txtblkOutput.Text += 
 myEmployeeInstance.myPerson.Print(); // should be the value you entered.
    }


Finalmente, o método de atualização vai mudar a propriedade era da classe "Person". Quando ele é criado com uma existência singleton e, mais tarde referenciada com uma instância singleton, você será capaz de ver o valor atualizado, mesmo que a instância inicial da classe "Person" não existe ou está fora do escopo.


private void btnUpdate_Click(object sender, RoutedEventArgs e)
{     
  IEmployee myEmployeeInstance = myContainer.Resolve<IEmployee>
     ("EmployeeWithPersonSingleton");
  try
  {
    myEmployeeInstance.myPerson.Age = Convert.ToInt32(this.txtNewValue.Text);        
  }
  catch (Exception)
  {
    this.txtblkOutput.Text += "Please enter an integer.";
  }
}

Classes

A classe "Employee " é a classe que é sempre instanciado em nossa aplicação, mas usando abordagem injeção de construtor, somos capazes de criar a classe "Person".

using NotificationLibrary.Interfaces;

namespace NotificationLibrary.Model
{
  public class Employee : NotificationLibrary.Interfaces.IEmployee
  {
    public IPerson myPerson { set; get; }
    public Employee(Person baseObject)
    {
      myPerson = baseObject;
    }
    public string Print() { return "Hello from an employee.\n"; }   
  }
}

A classe pessoa tem uma propriedade chamada "age (idade)", que possamos atualizar e depois podemos ver esta propriedade no final do aplicativo, clicando nos outros botões únicos a ver o valor de idade previamente salvos.

namespace NotificationLibrary.Model
{
  public class Person : NotificationLibrary.Interfaces.IPerson
  {
    private int age;
    public int Age
    {
      get { return this.age; }
      set { this.age = value; }
    }
    public Person() { this.Age = 0; }
    public string Print() { return "Hello from a person that is 
      constructor injected into this onject. My age is " + this.Age.ToString() + "\n"; }
  }
}
Interfaces
using System;
using NotificationLibrary.Model;
namespace NotificationLibrary.Interfaces
{
  public interface IEmployee
  {    
    IPerson myPerson { set; get; }
    string Print();
  }
}
using System;
namespace NotificationLibrary.Interfaces
{
  public interface IPerson
  {
    string Print();
    int Age { set; get; }
  }
}