Esercizio - Passare a un contenitore IoC e inserire la dipendenza nel codice

Completato

In questo esercizio finale si sostituiranno gli schemi usati negli esercizi precedenti con uno schema con inserimento delle dipendenze o contenitore IoC. Il contenitore fornirà sia il QuoteLoader che il motore di sintesi vocale come parte del costruttore.

Questo esercizio è una continuazione dell'esercizio precedente. Usare la soluzione esistente come punto di partenza per questi passaggi. Per iniziare da qui, aprire la soluzione completata dalla cartella exercise2>final nella copia del repository dell'esercizio clonato o scaricato.

Aggiungere il contenitore IoC

  1. La cartella exercise3>assets contiene una classe SimpleContainer. Questa classe è un'implementazione semplice di un contenitore IoC che consente di registrare e creare tipi. Aggiungerla alla libreria .NET Standard.

    Nota

    Questo esempio è intenzionalmente semplificato, in modo da illustrare chiaramente i concetti e consentire di tracciare il codice. Per le applicazioni non di esempio è consigliabile usare un contenitore IoC reale.

  2. Esaminare il codice. Sono presenti tre metodi di particolare interesse:

    Metodo Descrizione
    Register Questo metodo viene usato per registrare le astrazioni note con il contenitore. Si registra una funzione di creazione con il tipo di astrazione per rendere possibile la creazione. In alternativa, è sufficiente passare il tipo di implementazione se ha un costruttore predefinito. Questo metodo consente al contenitore di risolvere le interfacce in tipi, in modo simile a un localizzatore di servizi. Volendo, a questo scopo si può usare anche un localizzatore di servizi.
    FactoryFor<T> Questo metodo viene usato per creare un metodo factory che crea il tipo T. Restituisce un metodo Func<T> in cui l'implementazione passa attraverso il contenitore per generare il tipo. Questo metodo può essere usato per creare metodi factory in modo che i consumer non debbano avere un riferimento al contenitore stesso.
    Create Questo metodo viene usato per creare tipi. Se il tipo è registrato, viene usato il metodo di registrazione associato. In caso contrario, se il tipo ha un costruttore predefinito pubblico, questo viene usato per creare l'oggetto. Se il tipo non ha un costruttore predefinito pubblico, il codice seleziona il primo costruttore pubblico e tenta di creare ognuno dei parametri obbligatori chiamando in modo ricorsivo Create, quindi costruisce l'oggetto finale passando i parametri. Se non riesce a creare il tipo specificato, il codice restituisce null.

Aggiornare il costruttore QuoteManager con la dipendenza IQuoteLoader

Successivamente, modificare il codice nella classe QuoteManager in modo da inserire la dipendenza IQuoteLoader come parametro del costruttore.

  1. Aprire la classe QuoteManager e aggiungere un nuovo parametro di tipo IQuoteLoader al costruttore. Assegnare il parametro passato a un campo. Rimuovere il codice esistente che usa lo schema Factory.

  2. Cambiare il costruttore in public, in modo che sia possibile crearlo esternamente.

  3. Modificare la proprietà statica Instance in una proprietà implementata automaticamente con un Set privato. Rimuovere il campo Lazy esistente. Assegnare la proprietà Instance nel costruttore. In questo caso, imporre l'uso dello schema Singleton aggiungendo un controllo per verificare che la proprietà non venga impostata più di una volta.

    public class QuoteManager
    {
        public static QuoteManager Instance { get; private set; }
    
        readonly IQuoteLoader loader;
        public IList<GreatQuoteViewModel> Quotes { get; private set; }
    
        public QuoteManager(IQuoteLoader loader)
        {
            if (Instance != null) {
              throw new Exception("Can only create a single QuoteManager.");
            }
            Instance = this;
            this.loader = loader;
            Quotes = new ObservableCollection<GreatQuoteViewModel>(loader.Load());
        }
        ...
    }
    

Creare e registrare l'astrazione QuoteLoader con il contenitore IoC

Per concludere, creare il contenitore in ogni progetto specifico della piattaforma. Registrare l'implementazione IQuoteLoader con il contenitore. Si userà poi il contenitore per creare QuoteManager, in modo che inserisca correttamente i parametri del costruttore necessari. Si ripeteranno i passaggi di base in ogni progetto (iOS e Android).

  1. Aprire il file che configura la factory che individua l'implementazione di IQuoteLoader. In iOS questo file è AppDelegate.cs. Android usa MainActivity.cs.
  2. Aggiungere un nuovo campo SimpleContainer alla classe a livello di applicazione. Creare un'istanza del contenitore.
  3. Rimuovere il codice di registrazione factory corrente. Il codice è in FinishedLaunching in iOS e in OnCreate in Android.
  4. Registrare l'astrazione IQuoteLoader con le implementazioni specifiche della piattaforma usando il metodo Register<T, TImpl> del contenitore.
  5. Usare il metodo Create del contenitore per creare l'istanza di QuoteManager. Assegnarla al campo attualmente in uso.
  6. Eseguire l'applicazione in una delle piattaforme supportate per verificare che funzioni ancora tutto. Provare a inserire un punto di interruzione nel costruttore QuoteManager per vedere il parametro inserito. Esaminare lo stack di chiamate per vedere come è stato ottenuto.

Implementazione iOS

// AppDelegate.cs
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
readonly SimpleContainer container = new SimpleContainer();

public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
{
    container.Register<IQuoteLoader, QuoteLoader>();
    container.Create<QuoteManager>();

    ServiceLocator.Instance.Add<ITextToSpeech, TextToSpeechService>();

    global::Xamarin.Forms.Forms.Init();

    LoadApplication(new App());

    return base.FinishedLaunching(uiApplication, launchOptions);
}
}

Implementazione Android

// MainActivity.cs
[Activity(Label = "@string/app_name", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    readonly SimpleContainer container = new SimpleContainer();

    protected override void OnCreate(Bundle savedInstanceState)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;

        base.OnCreate(savedInstanceState);

        container.Register<IQuoteLoader, QuoteLoader>();
        container.Create<QuoteManager>();

        ServiceLocator.Instance.Add<ITextToSpeech, TextToSpeechService>();

        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

        LoadApplication(new App());
    }
    ...
}

Aggiornare il costruttore QuoteManager con la dipendenza ITextToSpeech

Il contenitore viene usato per creare QuoteManager con un'interfaccia IQuoteLoader passata per caricare le citazioni. QuoteManager usa anche l'implementazione ITextToSpeech individuata dal localizzatore di servizi.

  1. Aprire la classe QuoteManager e creare un nuovo campo di tipo ITextToSpeech.

  2. Aggiungere al costruttore un nuovo parametro di tipo ITextToSpeech. Assegnare il parametro passato al campo creato nella procedura precedente.

  3. Rimuovere il codice esistente che usa l'approccio del localizzatore di servizi nel metodo SayQuote.

    Il codice della classe QuoteManager dovrebbe essere simile al seguente:

    public class QuoteManager
    {
        private IQuoteLoader loader;
        private ITextToSpeech tts;
        ...
    
        public QuoteManager(IQuoteLoader loader, ITextToSpeech tts)
        {
            if (Instance != null)
            {
                throw new Exception("Can only create a single QuoteManager.");
            }
            Instance = this;
    
            this.loader = loader;
            this.tts = tts;
            Quotes = new ObservableCollection<GreatQuoteViewModel>(loader.Load());
        }
    
        ...
    
        public void SayQuote(GreatQuoteViewModel quote)
        {
            if (quote == null)
                throw new ArgumentNullException("No quote set");
    
            if (tts != null)
            {
                var text = quote.QuoteText;
    
                if (!string.IsNullOrWhiteSpace(quote.Author))
                    text += $" by {quote.Author}";
    
                tts.Speak(text);
            }
        }
    }
    

Aggiornare il codice specifico della piattaforma

  1. Aprire il file che configura la factory che individua l'implementazione di IQuoteLoader. In iOS questo file è AppDelegate.cs. Android usa MainActivity.cs.

  2. Registrare l'astrazione ITextToSpeech con le implementazioni specifiche della piattaforma appropriate usando il metodo Register<T, TImpl> del contenitore.

  3. Rimuovere la registrazione del localizzatore di servizi.

    Il codice del metodo FinishedLaunching ora dovrebbe essere simile al seguente:

    public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
    {
        container.Register<IQuoteLoader, QuoteLoader>();
        container.Register<ITextToSpeech, TextToSpeechService>();
        container.Create<QuoteManager>();
    
        global::Xamarin.Forms.Forms.Init();
    
        LoadApplication(new App());
    
        return base.FinishedLaunching(uiApplication, launchOptions);
    }
    

    Il codice del metodo OnCreate ora dovrebbe essere simile al seguente:

    protected override void OnCreate(Bundle savedInstanceState)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;
    
        base.OnCreate(savedInstanceState);
    
        container.Register<IQuoteLoader, QuoteLoader>();
        container.Register<ITextToSpeech, TextToSpeechService>();
        container.Create<QuoteManager>();
    
        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
    
        LoadApplication(new App());
    }
    

Eseguire l'applicazione

  1. Compilare ed eseguire l'applicazione nel maggior numero di implementazioni possibili.
  2. Impostare alcuni punti di interruzione e tracciare il contenitore IoC per vedere come trova l'implementazione e crea il servizio.

In questo esercizio si è partiti da un'applicazione esistente e si è usato un contenitore di inserimento delle dipendenze per effettuare l'accoppiamento debole delle dipendenze.

È possibile visualizzare la soluzione completata nella cartella exercise3>final della copia del repository dell'esercizio clonato o scaricato.