Condividi tramite


Inserimento delle dipendenze

Nota

Questo eBook è stato pubblicato nella primavera del 2017 e non è stato aggiornato da allora. C'è molto nel libro che rimane prezioso, ma alcuni dei materiali sono obsoleti.

In genere, un costruttore di classe viene richiamato quando si crea un'istanza di un oggetto e tutti i valori necessari all'oggetto vengono passati come argomenti al costruttore. Questo è un esempio di inserimento delle dipendenze e, in particolare, è noto come inserimento del costruttore. Le dipendenze necessarie per l'oggetto vengono inserite nel costruttore.

Specificando le dipendenze come tipi di interfaccia, l'inserimento delle dipendenze consente di separare i tipi concreti dal codice che dipende da questi tipi. In genere usa un contenitore che contiene un elenco di registrazioni e mapping tra interfacce e tipi astratti e i tipi concreti che implementano o estendono questi tipi.

Esistono anche altri tipi di inserimento delle dipendenze, ad esempio l'inserimento di setter di proprietà e l'inserimento di chiamate al metodo, ma sono meno comunemente visti. Pertanto, questo capitolo si concentrerà esclusivamente sull'esecuzione dell'inserimento del costruttore con un contenitore di inserimento delle dipendenze.

Introduzione all'inserimento delle dipendenze

L'inserimento delle dipendenze è una versione specializzata del modello Inversion of Control (IoC), in cui il problema invertito è il processo di recupero della dipendenza richiesta. Con l'inserimento delle dipendenze, un'altra classe è responsabile dell'inserimento di dipendenze in un oggetto in fase di esecuzione. Nell'esempio di codice seguente viene illustrato come strutturare la classe ProfileViewModel quando si usa l'inserimento delle dipendenze:

public class ProfileViewModel : ViewModelBase  
{  
    private IOrderService _orderService;  

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

Il ProfileViewModel costruttore riceve un'istanza IOrderService come argomento, inserita da un'altra classe. L'unica dipendenza nella ProfileViewModel classe è sul tipo di interfaccia. Pertanto, la ProfileViewModel classe non ha alcuna conoscenza della classe responsabile della creazione di un'istanza dell'oggetto IOrderService . La classe responsabile della creazione di un'istanza dell'oggetto e dell'inserimento IOrderService nella ProfileViewModel classe è nota come contenitore di inserimento delle dipendenze.

I contenitori di inserimento delle dipendenze riducono l'accoppiamento tra oggetti fornendo una funzionalità per creare istanze di classe e gestirle in base alla configurazione del contenitore. Durante la creazione degli oggetti, il contenitore inserisce tutte le dipendenze richieste dall'oggetto. Se tali dipendenze non sono ancora state create, il contenitore crea e risolve prima le relative dipendenze.

Nota

L'inserimento delle dipendenze può anche essere implementato manualmente usando le factory. Tuttavia, l'uso di un contenitore offre funzionalità aggiuntive, ad esempio la gestione della durata e la registrazione tramite l'analisi degli assembly.

L'uso di un contenitore di inserimento delle dipendenze offre diversi vantaggi:

  • Un contenitore rimuove la necessità di una classe per individuare le relative dipendenze e gestirle.
  • Un contenitore consente il mapping delle dipendenze implementate senza influire sulla classe .
  • Un contenitore facilita la testabilità consentendo la simulazione delle dipendenze.
  • Un contenitore aumenta la manutenibilità consentendo di aggiungere facilmente nuove classi all'app.

Nel contesto di un'app Xamarin.Forms che usa MVVM, in genere verrà usato un contenitore di inserimento delle dipendenze per la registrazione e la risoluzione dei modelli di visualizzazione e per la registrazione dei servizi e l'inserimento nei modelli di visualizzazione.

Sono disponibili molti contenitori di inserimento delle dipendenze, con l'app per dispositivi mobili eShopOnContainers usando TinyIoC per gestire l'istanza delle classi del modello di visualizzazione e del servizio nell'app. TinyIoC è stato scelto dopo aver valutato una serie di contenitori diversi e offre prestazioni superiori sulle piattaforme per dispositivi mobili rispetto alla maggior parte dei contenitori noti. Facilita la creazione di app ad accoppiamento libero e fornisce tutte le funzionalità comunemente disponibili nei contenitori di inserimento delle dipendenze, inclusi i metodi per registrare i mapping dei tipi, risolvere gli oggetti, gestire la durata degli oggetti e inserire oggetti dipendenti nei costruttori di oggetti risolti. Per altre informazioni su TinyIoC, vedere TinyIoC in github.com.

In TinyIoC il TinyIoCContainer tipo fornisce il contenitore di inserimento delle dipendenze. La figura 3-1 mostra le dipendenze quando si usa questo contenitore, che crea un'istanza di un IOrderService oggetto e la inserisce nella ProfileViewModel classe .

Dependencies example when using dependency injection

Figura 3-1: Dipendenze quando si usa l'inserimento delle dipendenze

In fase di esecuzione, il contenitore deve conoscere l'implementazione dell'interfaccia di cui deve creare un'istanza IOrderService prima di poter creare un'istanza di un ProfileViewModel oggetto. Questo implica:

  • Contenitore che decide come creare un'istanza di un oggetto che implementa l'interfaccia IOrderService . Questa operazione è nota come registrazione.
  • Contenitore che crea un'istanza dell'oggetto che implementa l'interfaccia IOrderService e l'oggetto ProfileViewModel . Questa operazione è nota come risoluzione.

Alla fine, l'app terminerà l'uso dell'oggetto ProfileViewModel e diventerà disponibile per l'operazione di Garbage Collection. A questo punto, il Garbage Collector deve eliminare l'istanza IOrderService se altre classi non condividono la stessa istanza.

Suggerimento

Scrivere codice indipendente dal contenitore. Provare sempre a scrivere codice indipendente dal contenitore per separare l'app dal contenitore di dipendenze specifico in uso.

Registrazione

Prima che le dipendenze possano essere inserite in un oggetto, i tipi delle dipendenze devono essere prima registrati con il contenitore. La registrazione di un tipo comporta in genere il passaggio del contenitore di un'interfaccia e un tipo concreto che implementa l'interfaccia.

Esistono due modi per registrare i tipi e gli oggetti nel contenitore tramite il codice:

  • Registrare un tipo o un mapping con il contenitore. Se necessario, il contenitore compilerà un'istanza del tipo specificato.
  • Registrare un oggetto esistente nel contenitore come singleton. Se necessario, il contenitore restituirà un riferimento all'oggetto esistente.

Suggerimento

I contenitori di inserimento delle dipendenze non sono sempre adatti. L'inserimento delle dipendenze introduce complessità e requisiti aggiuntivi che potrebbero non essere appropriati o utili per le piccole app. Se una classe non ha dipendenze o non è una dipendenza per altri tipi, potrebbe non essere opportuno inserirla nel contenitore. Inoltre, se una classe ha un singolo set di dipendenze che sono integrali al tipo e non cambierà mai, potrebbe non essere opportuno inserirla nel contenitore.

La registrazione dei tipi che richiedono l'inserimento delle dipendenze deve essere eseguita in un singolo metodo in un'app e questo metodo deve essere richiamato all'inizio del ciclo di vita dell'app per garantire che l'app sia consapevole delle dipendenze tra le relative classi. Nell'app per dispositivi mobili eShopOnContainers questa operazione viene eseguita dalla ViewModelLocator classe , che compila l'oggetto TinyIoCContainer ed è l'unica classe nell'app che contiene un riferimento a tale oggetto. L'esempio di codice seguente illustra come l'app per dispositivi mobili eShopOnContainers dichiara l'oggetto TinyIoCContainer nella ViewModelLocator classe :

private static TinyIoCContainer _container;

I tipi vengono registrati nel ViewModelLocator costruttore. Questa operazione viene ottenuta creando prima un'istanza TinyIoCContainer di , illustrata nell'esempio di codice seguente:

_container = new TinyIoCContainer();

I tipi vengono quindi registrati con l'oggetto e l'esempio TinyIoCContainer di codice seguente illustra la forma più comune di registrazione del tipo:

_container.Register<IRequestProvider, RequestProvider>();

Il Register metodo illustrato di seguito esegue il mapping di un tipo di interfaccia a un tipo concreto. Per impostazione predefinita, ogni registrazione dell'interfaccia viene configurata come singleton in modo che ogni oggetto dipendente riceva la stessa istanza condivisa. Pertanto, nel contenitore esisterà solo una singola RequestProvider istanza, condivisa da oggetti che richiedono un'iniezione di un IRequestProvider tramite un costruttore.

I tipi concreti possono anche essere registrati direttamente senza un mapping da un tipo di interfaccia, come illustrato nell'esempio di codice seguente:

_container.Register<ProfileViewModel>();

Per impostazione predefinita, ogni registrazione di classe concreta viene configurata come istanza multipla in modo che ogni oggetto dipendente riceva una nuova istanza. Pertanto, quando l'oggetto ProfileViewModel viene risolto, verrà creata una nuova istanza e il contenitore inserirà le dipendenze necessarie.

Risoluzione

Dopo la registrazione di un tipo, può essere risolto o inserito come dipendenza. Quando un tipo viene risolto e il contenitore deve creare una nuova istanza, inserisce eventuali dipendenze nell'istanza di .

In genere, quando viene risolto un tipo, si verifica una delle tre operazioni seguenti:

  1. Se il tipo non è stato registrato, il contenitore genera un'eccezione.
  2. Se il tipo è stato registrato come singleton, il contenitore restituisce l'istanza singleton. Se è la prima volta che viene chiamato il tipo, il contenitore lo crea, se necessario, e mantiene un riferimento a esso.
  3. Se il tipo non è stato registrato come singleton, il contenitore restituisce una nuova istanza e non ne mantiene un riferimento.

L'esempio di codice seguente mostra come è possibile risolvere il RequestProvider tipo registrato in precedenza con TinyIoC:

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

In questo esempio viene chiesto a TinyIoC di risolvere il tipo concreto per il IRequestProvider tipo, insieme a eventuali dipendenze. In genere, il Resolve metodo viene chiamato quando è necessaria un'istanza di un tipo specifico. Per informazioni sul controllo della durata degli oggetti risolti, vedere Gestione della durata degli oggetti risolti.

L'esempio di codice seguente mostra come l'app per dispositivi mobili eShopOnContainers crea un'istanza dei tipi di modello di visualizzazione e le relative dipendenze:

var viewModel = _container.Resolve(viewModelType);

In questo esempio viene chiesto a TinyIoC di risolvere il tipo di modello di visualizzazione per un modello di visualizzazione richiesto e il contenitore risolverà anche eventuali dipendenze. Quando si risolve il ProfileViewModel tipo, le dipendenze da risolvere sono un ISettingsService oggetto e un IOrderService oggetto . Poiché le registrazioni dell'interfaccia sono state usate durante la registrazione delle SettingsService classi e OrderService , TinyIoC restituisce le istanze singleton per le SettingsService classi e OrderService e quindi le passa al costruttore della ProfileViewModel classe . Per altre informazioni su come l'app per dispositivi mobili eShopOnContainers costruisce i modelli di visualizzazione e li associa alle visualizzazioni, vedere Creazione automatica di un modello di visualizzazione con un localizzatore di modelli di visualizzazione.

Nota

La registrazione e la risoluzione dei tipi con un contenitore comportano un costo in termini di prestazioni perché il contenitore usa la reflection per la creazione di ogni tipo, soprattutto se le dipendenze vengono ricostruite per la navigazione di ogni pagina nell'app. Se le dipendenze presenti sono numerose o complete, il costo della creazione può aumentare in modo significativo.

Gestione della durata degli oggetti risolti

Dopo aver registrato un tipo usando una registrazione di classe concreta, il comportamento predefinito per TinyIoC consiste nel creare una nuova istanza del tipo registrato ogni volta che il tipo viene risolto o quando il meccanismo di dipendenza inserisce istanze in altre classi. In questo scenario, il contenitore non contiene un riferimento all'oggetto risolto. Tuttavia, quando si registra un tipo usando la registrazione dell'interfaccia, il comportamento predefinito per TinyIoC consiste nel gestire la durata dell'oggetto come singleton. Pertanto, l'istanza rimane nell'ambito mentre il contenitore è nell'ambito e viene eliminato quando il contenitore esce dall'ambito e viene sottoposto a Garbage Collection oppure quando il codice elimina in modo esplicito il contenitore.

È possibile eseguire l'override del comportamento di registrazione TinyIoC predefinito usando i metodi fluent AsSingleton e AsMultiInstance API. Ad esempio, il AsSingleton metodo può essere usato con il Register metodo , in modo che il contenitore crei o restituisca un'istanza singleton di un tipo quando si chiama il Resolve metodo . Nell'esempio di codice seguente viene illustrato come Viene richiesto a TinyIoC di creare un'istanza singleton della LoginViewModel classe :

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

La prima volta che il LoginViewModel tipo viene risolto, il contenitore crea un nuovo LoginViewModel oggetto e ne mantiene un riferimento. In tutte le risoluzioni successive di LoginViewModel, il contenitore restituisce un riferimento all'oggetto LoginViewModel creato in precedenza.

Nota

I tipi registrati come singleton vengono eliminati quando il contenitore viene eliminato.

Riepilogo

L'inserimento delle dipendenze consente di separare i tipi concreti dal codice che dipende da questi tipi. In genere usa un contenitore che contiene un elenco di registrazioni e mapping tra interfacce e tipi astratti e i tipi concreti che implementano o estendono questi tipi.

TinyIoC è un contenitore leggero che offre prestazioni superiori sulle piattaforme mobili rispetto alla maggior parte dei contenitori noti. Facilita la creazione di app ad accoppiamento libero e fornisce tutte le funzionalità comunemente disponibili nei contenitori di inserimento delle dipendenze, inclusi i metodi per registrare mapping dei tipi, risolvere oggetti, gestire la durata degli oggetti e inserire oggetti dipendenti nei costruttori di oggetti risolti.