Partilhar via


Injeção de dependência

Gorjeta

Este conteúdo é um excerto do eBook, Enterprise Application Patterns Using .NET MAUI, disponível no .NET Docs ou como um PDF para download gratuito que pode ser lido offline.

Padrões de aplicativos corporativos usando a miniatura da capa do eBook .NET MAUI .

Normalmente, um construtor de classe é invocado ao instanciar um objeto, e quaisquer valores que o objeto precisa são passados como argumentos para o construtor. Este é um exemplo de injeção de dependência conhecida como injeção de construtor. As dependências que o objeto precisa são injetadas no construtor.

Ao especificar dependências como tipos de interface, a injeção de dependência permite dissociar os tipos concretos do código que depende desses tipos. Ele geralmente 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.

Existem 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á exclusivamente na execução da injeção do 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 Inversão de Controle (IoC), onde 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 tempo de execução. O exemplo de código a seguir mostra como a classe é estruturada ao usar a ProfileViewModel injeção de dependência:

private readonly ISettingsService _settingsService;
private readonly IAppEnvironmentService _appEnvironmentService;

public ProfileViewModel(
    IAppEnvironmentService appEnvironmentService,
    IDialogService dialogService, 
    INavigationService navigationService, 
    ISettingsService settingsService)
    : base(dialogService, navigationService, settingsService)
{
    _appEnvironmentService = appEnvironmentService;
    _settingsService = settingsService;

    // Omitted for brevity
}

O ProfileViewModel construtor recebe várias instâncias de objeto de interface como argumentos injetados por outra classe. A única dependência na ProfileViewModel classe é nos tipos de interface. Portanto, a ProfileViewModel classe não tem nenhum conhecimento da classe responsável por instanciar os objetos de interface. A classe responsável por instanciar os objetos de interface e inseri-los na ProfileViewModel classe é conhecida como contêiner de injeção de dependência.

Os contêineres de injeção de dependência reduzem o acoplamento entre objetos, fornecendo um recurso para instanciar instâncias de classe e gerenciar sua vida útil com base na configuração do contêiner. Durante a criação do objeto, 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.

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

  • Um contêiner elimina 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 aplicativo .NET MAUI que usa MVVM, um contêiner de injeção de dependência normalmente será usado para registrar e resolver modos de exibição, 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 no .NET; o aplicativo multiplataforma eShop usa Microsoft.Extensions.DependencyInjection para gerenciar a instanciação de visualizações, modelos de exibição e classes de serviço no aplicativo. Microsoft.Extensions.DependencyInjection Facilita a criação de aplicativos com acoplamento flexível e fornece todos os recursos comumente encontrados em contêineres de injeção de dependência, incluindo métodos para registrar mapeamentos de tipo e instâncias de objetos, resolver objetos, gerenciar tempos de vida de objetos e injetar objetos dependentes em construtores de objetos que ele resolve. Para obter mais informações sobre Microsoft.Extensions.DependencyInjectiono , consulte Injeção de dependência no .NET.

No .NET MAUI, a MauiProgram classe chamará o CreateMauiApp método para criar um MauiAppBuilder objeto. O MauiAppBuilder objeto tem uma Services propriedade do tipo IServiceCollection, que fornece um local para registrar nossos componentes, como modos de exibição, modelos de exibição e serviços para injeção de dependência. Todos os componentes registrados com a Services propriedade serão fornecidos ao contêiner de injeção de dependência quando o MauiAppBuilder.Build método for chamado.

No tempo de execução, o contêiner deve saber qual implementação dos serviços está sendo solicitada para instanciá-los para os objetos solicitados. No aplicativo multiplataforma eShop, as IAppEnvironmentServiceinterfaces , IDialogService, INavigationServicee ISettingsService precisam ser resolvidas antes que ele possa instanciar um ProfileViewModel objeto. Isso envolve o contêiner executando as seguintes ações:

  • Decidir como instanciar um objeto que implementa a interface. Isso é conhecido como registro.
  • Instanciar o objeto que implementa a interface necessária 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 deve descartar quaisquer implementações de interface de curta duração se outras classes não compartilharem a mesma instância.

Registo

Antes que as dependências possam ser injetadas em um objeto, os tipos de dependências devem primeiro ser registrados no contêiner. Registrar um tipo 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 de código:

  • Registre um tipo ou mapeamento com o contêiner. Isto é conhecido como registo transitório. 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.

Nota

Os recipientes 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 são parte integrante do tipo e nunca serão 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 realizado em um único método em um aplicativo. Esse método deve ser invocado no início do ciclo de vida do aplicativo para garantir que ele esteja ciente das dependências entre suas classes. O aplicativo multiplataforma eShop executa este método MauiProgram.CreateMauiApp . O exemplo de código a seguir mostra como o aplicativo multiplataforma eShop declara o CreateMauiApp na MauiProgram classe:

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
        => MauiApp.CreateBuilder()
            .UseMauiApp<App>()
            // Omitted for brevity            
            .RegisterAppServices()
            .RegisterViewModels()
            .RegisterViews()
            .Build();
}

O MauiApp.CreateBuilder método cria um MauiAppBuilder objeto que podemos usar para registrar nossas dependências. Muitas dependências no aplicativo multiplataforma eShop precisam ser registradas, portanto, os métodos RegisterAppServicesde extensão , RegisterViewModelse RegisterViews foram criados para ajudar a fornecer um fluxo de trabalho de registro organizado e sustentável. O código a seguir mostra o RegisterViewModels método:

public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ViewModels.MainViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.LoginViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.BasketViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.CatalogViewModel>();
    mauiAppBuilder.Services.AddSingleton<ViewModels.ProfileViewModel>();

    mauiAppBuilder.Services.AddTransient<ViewModels.CheckoutViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.OrderDetailViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.SettingsViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignViewModel>();
    mauiAppBuilder.Services.AddTransient<ViewModels.CampaignDetailsViewModel>();

    return mauiAppBuilder;
}

Esse método recebe uma instância de , e podemos usar a Services propriedade para registrar nossos modelos de MauiAppBuilderexibição. Dependendo das necessidades do seu aplicativo, talvez seja necessário adicionar serviços com tempos de vida diferentes. A tabela a seguir fornece informações sobre quando você pode querer escolher esses diferentes tempos de vida de registro:

Método Description
AddSingleton<T> Criará uma única instância do objeto que permanecerá durante o tempo de vida do aplicativo.
AddTransient<T> Criará uma nova instância do objeto quando solicitado durante a resolução. Os objetos transitórios não têm um tempo de vida predefinido, mas normalmente seguem o tempo de vida de seu host.

Nota

Os modelos de visualização não herdam de uma interface, por isso só precisam do seu tipo concreto fornecido aos AddSingleton<T> AddTransient<T> e métodos.

O CatalogViewModel é usado perto da raiz do aplicativo e deve estar sempre disponível, portanto, registrá-lo com AddSingleton<T> é benéfico. Outros modelos de exibição, como CheckoutViewModel e OrderDetailViewModel são navegados situacionalmente ou são usados posteriormente no aplicativo. Suponha que você saiba que tem um componente que nem sempre pode ser usado. Nesse caso, se for de memória ou computacionalmente intensivo ou exigir dados just-in-time, pode ser um candidato melhor para AddTransient<T> o registro.

Outra maneira comum de adicionar serviços é usando os AddSingleton<TService, TImplementation> métodos e AddTransient<TService, TImplementation> . Esses métodos tomam dois tipos de entrada: a definição da interface e a implementação concreta. Esse tipo de registro é melhor para casos em que você está implementando serviços baseados em interfaces. No exemplo de código abaixo, registramos nossa ISettingsService interface usando a SettingsService implementação:

public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder)
{
    mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
    // Omitted for brevity...
}

Uma vez que todos os serviços tenham sido registrados, o MauiAppBuilder.Build método deve ser chamado para criar nosso MauiApp e preencher nosso contêiner de injeção de dependência com todos os serviços registrados.

Importante

Uma vez que o Build método tenha sido chamado, o contêiner de injeção de dependência é imutável e não pode mais ser atualizado ou modificado. Certifique-se de que todos os serviços de que necessita na sua aplicação foram registados antes de ligar Build.

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 quaisquer 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 lançará uma exceção.
  2. Se o tipo tiver sido registrado como singleton, o contêiner retornará a instância singleton. Se esta for a primeira vez que o tipo é solicitado, o contêiner o cria, se necessário, e mantém uma referência a ele.
  3. Se o tipo tiver sido registrado como transitório, o contêiner retornará uma nova instância e não manterá uma referência a ele.

O .NET MAUI oferece várias maneiras de resolver componentes registrados com base em suas necessidades. A maneira mais direta de obter acesso ao contêiner de injeção de dependência é usando Element o Handler.MauiContext.Services. Um exemplo disso é mostrado abaixo:

var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();

Isso pode ser útil se você precisar resolver um serviço de dentro Element ou de fora do construtor do seu Element.

Atenção

Existe a possibilidade de que a Handler propriedade do seu Element pode ser nula, então esteja ciente de que você pode precisar lidar com essas situações. Para obter mais informações, consulte Ciclo de vida do manipulador no Centro de Documentação da Microsoft.

Se estiver usando o Shell controle para .NET MAUI, ele chamará implicitamente o contêiner de injeção de dependência para criar nossos objetos durante a navegação. Ao configurar nosso Shell controle, o Routing.RegisterRoute método vinculará um caminho de rota a um View , conforme mostrado no exemplo abaixo:

Routing.RegisterRoute("Filter", typeof(FiltersView));

Durante Shell a FiltersViewnavegação, ele procurará registros do , e se algum for encontrado, ele criará essa exibição e injetará quaisquer dependências no construtor. Como mostrado no exemplo de código abaixo, o será injetado CatalogViewModel no FiltersView:

namespace eShop.Views;

public partial class FiltersView : ContentPage
{
    public FiltersView(CatalogViewModel viewModel)
    {
        BindingContext = viewModel;

        InitializeComponent();
    }
}

Gorjeta

O contêiner de injeção de dependência é ótimo para criar instâncias de modelo de exibição. Se um modelo de exibição tiver dependências, ele lidará com a criação e a injeção de todos os serviços necessários. Apenas certifique-se de registrar seus modelos de exibição e quaisquer dependências que eles possam ter com o CreateMauiApp método na MauiProgram classe.

Resumo

A injeção de dependência permite a dissociação de tipos concretos do código que depende desses tipos. Ele normalmente 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.

Microsoft.Extensions.DependencyInjection Facilita a criação de aplicativos com acoplamento flexível e fornece todos os recursos comumente encontrados em contêineres de injeção de dependência, incluindo métodos para registrar mapeamentos de tipo e instâncias de objetos, resolver objetos, gerenciar tempos de vida de objetos e injetar objetos dependentes em construtores de objetos que ele resolve.