Partilhar via


Injeção de dependência

Observação

Este eBook foi publicado na primavera de 2017 e não foi atualizado desde então. Há muito no livro que permanece valioso, mas parte do material está desatualizado.

Normalmente, um construtor de classe é invocado ao instanciar um objeto e todos os valores necessários pelo objeto são passados como argumentos para o construtor. Este é um exemplo de injeção de dependência e, especificamente, é conhecido como injeção de construtor. As dependências de que o objeto precisa são injetadas no construtor.

Ao especificar dependências como tipos de interface, a injeção de dependência permite a desacoplamento dos tipos concretos do código que depende desses tipos. Geralmente, ele usa um contêiner que contém uma lista de registros e mapeamentos entre interfaces e tipos abstratos e os tipos concretos que implementam ou estendem esses tipos.

Há também outros tipos de injeção de dependência, como injeção de setter de propriedade e injeção de chamada de método, mas eles são menos comumente vistos. Portanto, este capítulo se concentrará apenas na execução da injeção de construtor com um contêiner de injeção de dependência.

Introdução à injeção de dependência

A injeção de dependência é uma versão especializada do padrão de IoC (Inversão de Controle), em que a preocupação que está sendo invertida é o processo de obtenção da dependência necessária. Com a injeção de dependência, outra classe é responsável por injetar dependências em um objeto em runtime. O exemplo de código a seguir mostra como a classe ProfileViewModel é estruturada ao usar a injeção de dependência:

public class ProfileViewModel : ViewModelBase  
{  
    private IOrderService _orderService;  

    public ProfileViewModel(IOrderService orderService)  
    {  
        _orderService = orderService;  
    }  
    ...  
}

O ProfileViewModel construtor recebe uma IOrderService instância como um argumento, injetado por outra classe. A única dependência na ProfileViewModel classe é no tipo de interface. Portanto, a ProfileViewModel classe não tem nenhum conhecimento da classe responsável por instanciar o IOrderService objeto. A classe responsável por instanciar o IOrderService objeto e inseri-lo na ProfileViewModel classe é conhecida como o contêiner de injeção de dependência.

Os contêineres de injeção de dependência reduzem o acoplamento entre objetos fornecendo uma instalação para instanciar instâncias de classe e gerenciar seu tempo de vida com base na configuração do contêiner. Durante a criação dos objetos, o contêiner injeta todas as dependências que o objeto requer nele. Se essas dependências ainda não tiverem sido criadas, o contêiner criará e resolverá suas dependências primeiro.

Observação

A injeção de dependência também pode ser implementada manualmente usando fábricas. No entanto, o uso de um contêiner fornece recursos adicionais, como gerenciamento de tempo de vida e registro por meio da verificação de assembly.

Há várias vantagens em usar um contêiner de injeção de dependência:

  • Um contêiner remove a necessidade de uma classe localizar suas dependências e gerenciar seus tempos de vida.
  • Um contêiner permite o mapeamento de dependências implementadas sem afetar a classe .
  • Um contêiner facilita a capacidade de teste permitindo que as dependências sejam simuladas.
  • Um contêiner aumenta a capacidade de manutenção, permitindo que novas classes sejam facilmente adicionadas ao aplicativo.

No contexto de um Xamarin.Forms aplicativo que usa MVVM, um contêiner de injeção de dependência normalmente será usado para registrar e resolver modelos de exibição e para registrar serviços e injetá-los em modelos de exibição.

Há muitos contêineres de injeção de dependência disponíveis, com o aplicativo móvel eShopOnContainers usando TinyIoC para gerenciar a instanciação de classes de modelo de exibição e de serviço no aplicativo. TinyIoC foi escolhido após avaliar vários contêineres diferentes e apresenta desempenho superior em plataformas móveis quando comparado com a maioria dos contêineres conhecidos. Ele facilita a criação de aplicativos acoplados livremente e fornece todos os recursos comumente encontrados em contêineres de injeção de dependência, incluindo métodos para registrar mapeamentos de tipo, resolve objetos, gerenciar tempos de vida de objetos e injetar objetos dependentes em construtores de objetos que ele resolve. Para obter mais informações sobre TinyIoC, consulte TinyIoC no github.com.

No TinyIoC, o TinyIoCContainer tipo fornece o contêiner de injeção de dependência. A Figura 3-1 mostra as dependências ao usar esse contêiner, que cria uma instância de um IOrderService objeto e o injeta na ProfileViewModel classe .

Exemplo de dependências ao usar a injeção de dependência

Figura 3-1: Dependências ao usar injeção de dependência

Em runtime, o contêiner deve saber qual implementação da IOrderService interface deve instanciar, antes de poder instanciar um ProfileViewModel objeto. Isso envolve:

  • O contêiner que decide como instanciar um objeto que implementa a IOrderService interface . Isso é conhecido como registro.
  • O contêiner que cria uma instância do objeto que implementa a IOrderService interface e o ProfileViewModel objeto . Isso é conhecido como resolução.

Eventualmente, o aplicativo terminará de usar o ProfileViewModel objeto e ele ficará disponível para coleta de lixo. Neste ponto, o coletor de lixo deverá descartar a IOrderService instância se outras classes não compartilharem a mesma instância.

Dica

Escrever código independente de contêiner. Sempre tente escrever código independente de contêiner para desacoplar o aplicativo do contêiner de dependência específico que está sendo usado.

Registro

Antes que as dependências possam ser injetadas em um objeto, os tipos das dependências devem primeiro ser registrados com o contêiner. O registro de um tipo normalmente envolve passar ao contêiner uma interface e um tipo concreto que implementa a interface.

Há duas maneiras de registrar tipos e objetos no contêiner por meio do código:

  • Registre um tipo ou mapeamento com o contêiner. Quando necessário, o contêiner criará uma instância do tipo especificado.
  • Registre um objeto existente no contêiner como um singleton. Quando necessário, o contêiner retornará uma referência ao objeto existente.

Dica

Os contêineres de injeção de dependência nem sempre são adequados. A injeção de dependência introduz complexidade e requisitos adicionais que podem não ser apropriados ou úteis para aplicativos pequenos. Se uma classe não tiver dependências ou não for uma dependência para outros tipos, talvez não faça sentido colocá-la no contêiner. Além disso, se uma classe tiver um único conjunto de dependências que sejam integrais ao tipo e nunca forem alteradas, talvez não faça sentido colocá-la no contêiner.

O registro de tipos que exigem injeção de dependência deve ser executado em um único método em um aplicativo, e esse método deve ser invocado no início do ciclo de vida do aplicativo para garantir que o aplicativo esteja ciente das dependências entre suas classes. No aplicativo móvel eShopOnContainers, isso é executado pela ViewModelLocator classe , que compila o TinyIoCContainer objeto e é a única classe no aplicativo que contém uma referência a esse objeto. O exemplo de código a ViewModelLocator seguir mostra como o aplicativo móvel eShopOnContainers declara o TinyIoCContainer objeto na classe :

private static TinyIoCContainer _container;

Os tipos são registrados no ViewModelLocator construtor. Isso é obtido pela criação de uma TinyIoCContainer instância, que é demonstrada no exemplo de código a seguir:

_container = new TinyIoCContainer();

Os tipos são então registrados com o TinyIoCContainer objeto e o exemplo de código a seguir demonstra a forma mais comum de registro de tipo:

_container.Register<IRequestProvider, RequestProvider>();

O Register método mostrado aqui mapeia um tipo de interface para um tipo concreto. Por padrão, cada registro de interface é configurado como um singleton para que cada objeto dependente receba a mesma instância compartilhada. Portanto, apenas uma única RequestProvider instância existirá no contêiner, que é compartilhado por objetos que exigem uma injeção de um IRequestProvider por meio de um construtor.

Tipos concretos também podem ser registrados diretamente sem um mapeamento de um tipo de interface, conforme mostrado no exemplo de código a seguir:

_container.Register<ProfileViewModel>();

Por padrão, cada registro concreto de classe é configurado como uma instância múltipla para que cada objeto dependente receba uma nova instância. Portanto, quando o ProfileViewModel for resolvido, uma nova instância será criada e o contêiner injetará suas dependências necessárias.

Resolução

Depois que um tipo é registrado, ele pode ser resolvido ou injetado como uma dependência. Quando um tipo está sendo resolvido e o contêiner precisa criar uma nova instância, ele injeta todas as dependências na instância.

Geralmente, quando um tipo é resolvido, uma das três coisas acontece:

  1. Se o tipo não tiver sido registrado, o contêiner gerará uma exceção.
  2. Se o tipo tiver sido registrado como singleton, o contêiner retornará a instância singleton. Se essa for a primeira vez que o tipo é chamado, o contêiner o cria, se necessário, e mantém uma referência a ele.
  3. Se o tipo não tiver sido registrado como um singleton, o contêiner retornará uma nova instância e não manterá uma referência a ele.

O exemplo de código a seguir mostra como o RequestProvider tipo que foi registrado anteriormente com TinyIoC pode ser resolvido:

var requestProvider = _container.Resolve<IRequestProvider>();

Neste exemplo, TinyIoC é solicitado a resolve o tipo concreto para o IRequestProvider tipo, juntamente com quaisquer dependências. Normalmente, o Resolve método é chamado quando uma instância de um tipo específico é necessária. Para obter informações sobre como controlar o tempo de vida de objetos resolvidos, consulte Gerenciando o tempo de vida de objetos resolvidos.

O exemplo de código a seguir mostra como o aplicativo móvel eShopOnContainers cria uma instância dos tipos de modelo de exibição e suas dependências:

var viewModel = _container.Resolve(viewModelType);

Neste exemplo, TinyIoC é solicitado a resolve o tipo de modelo de exibição para um modelo de exibição solicitado e o contêiner também resolve quaisquer dependências. Ao resolver o ProfileViewModel tipo, as dependências para resolve são um ISettingsService objeto e um IOrderService objeto . Como os registros de interface foram usados ao registrar as SettingsService classes e OrderService , TinyIoC retorna instâncias singleton para as SettingsService classes e e OrderService , em seguida, passa-as para o construtor da ProfileViewModel classe . Para obter mais informações sobre como o aplicativo móvel eShopOnContainers constrói modelos de exibição e os associa a exibições, consulte Criando automaticamente um modelo de exibição com um localizador de modelo de exibição.

Observação

Efetuar o registro e a resolução de tipos usando um contêiner tem um custo de desempenho devido ao uso da reflexão pelo contêiner para criar cada tipo, especialmente se as dependências estiverem sendo reconstruídas para cada navegação de página no aplicativo. Se houver muitas dependências ou se elas forem profundas, o custo da criação poderá aumentar significativamente.

Gerenciando o tempo de vida de objetos resolvidos

Depois de registrar um tipo usando um registro de classe concreto, o comportamento padrão para TinyIoC é criar uma nova instância do tipo registrado sempre que o tipo for resolvido ou quando o mecanismo de dependência injetar instâncias em outras classes. Nesse cenário, o contêiner não contém uma referência ao objeto resolvido. No entanto, ao registrar um tipo usando o registro de interface, o comportamento padrão para TinyIoC é gerenciar o tempo de vida do objeto como um singleton. Portanto, a instância permanece no escopo enquanto o contêiner está no escopo e é descartada quando o contêiner sai do escopo e é coletado como lixo ou quando o código descarta explicitamente o contêiner.

O comportamento de registro padrão do TinyIoC pode ser substituído usando os métodos fluente AsSingleton e AsMultiInstance de API. Por exemplo, o AsSingleton método pode ser usado com o Register método , para que o contêiner crie ou retorne uma instância singleton de um tipo ao chamar o Resolve método . O exemplo de código a seguir mostra como TinyIoC é instruído a criar uma instância singleton da LoginViewModel classe :

_container.Register<LoginViewModel>().AsSingleton();

Na primeira vez que o LoginViewModel tipo é resolvido, o contêiner cria um novo LoginViewModel objeto e mantém uma referência a ele. Em quaisquer resoluções subsequentes do LoginViewModel, o contêiner retorna uma referência ao LoginViewModel objeto que foi criado anteriormente.

Observação

Os tipos registrados como singletons são descartados quando o contêiner é descartado.

Resumo

A injeção de dependência permite a desacoplamento de tipos concretos do código que depende desses tipos. Normalmente, ele usa um contêiner que contém uma lista de registros e mapeamentos entre interfaces e tipos abstratos e os tipos concretos que implementam ou estendem esses tipos.

TinyIoC é um contêiner leve que apresenta desempenho superior em plataformas móveis quando comparado com a maioria dos contêineres conhecidos. Ele facilita a criação de aplicativos flexívelmente acoplados e fornece todos os recursos comumente encontrados em contêineres de injeção de dependência, incluindo métodos para registrar mapeamentos de tipo, resolve objetos, gerenciar tempos de vida de objetos e injetar objetos dependentes em construtores de objetos que ele resolve.