Esercizio - Passare a un contenitore IoC e inserire la dipendenza nel codice
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
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.
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 metodoFunc<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 restituiscenull
.
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.
Aprire la classe
QuoteManager
e aggiungere un nuovo parametro di tipoIQuoteLoader
al costruttore. Assegnare il parametro passato a un campo. Rimuovere il codice esistente che usa lo schema Factory.Cambiare il costruttore in
public
, in modo che sia possibile crearlo esternamente.Modificare la proprietà statica
Instance
in una proprietà implementata automaticamente con unSet
privato. Rimuovere il campoLazy
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).
- Aprire il file che configura la factory che individua l'implementazione di
IQuoteLoader
. In iOS questo file è AppDelegate.cs. Android usa MainActivity.cs. - Aggiungere un nuovo campo
SimpleContainer
alla classe a livello di applicazione. Creare un'istanza del contenitore. - Rimuovere il codice di registrazione factory corrente. Il codice è in
FinishedLaunching
in iOS e inOnCreate
in Android. - Registrare l'astrazione
IQuoteLoader
con le implementazioni specifiche della piattaforma usando il metodoRegister<T, TImpl>
del contenitore. - Usare il metodo
Create
del contenitore per creare l'istanza diQuoteManager
. Assegnarla al campo attualmente in uso. - 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.
Aprire la classe
QuoteManager
e creare un nuovo campo di tipoITextToSpeech
.Aggiungere al costruttore un nuovo parametro di tipo
ITextToSpeech
. Assegnare il parametro passato al campo creato nella procedura precedente.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
Aprire il file che configura la factory che individua l'implementazione di
IQuoteLoader
. In iOS questo file è AppDelegate.cs. Android usa MainActivity.cs.Registrare l'astrazione
ITextToSpeech
con le implementazioni specifiche della piattaforma appropriate usando il metodoRegister<T, TImpl>
del contenitore.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
- Compilare ed eseguire l'applicazione nel maggior numero di implementazioni possibili.
- 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.