However this is at compile time that the dependency injection occurs.
Il tipo di servizio è definito al momento della compilazione. Questo è normalmente sotto forma di un'interfaccia o di una classe di base (a volte astratta). Il punto chiave qui è principio di sostituzione di Liskovs
It states that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.)
Quindi conosciamo il contratto del tipo in fase di compilazione
Then what does it mean that dependency injection also occurs at Runtime?
Quando si implementa DI, ad esempio, utilizzando un contenitore Inversion of Control il tipo di implementazione non è noto fino al runtime.
Esaminiamo un esempio di base per preparare un caffè usando Iniettore semplice come contenitore Inversion of Control e NUnit per testare il codice.
Per prima cosa definiamo la tazza e il nostro tipo di servizio:
public class Cup
{
public List<string> ingredients = new List<string>();
}
public interface ICoffeeService
{
Cup GetCoffee();
}
E un'implementazione del servizio:
public class CoffeeService : ICoffeeService
{
public Cup GetCoffee()
{
Cup cup = new Cup();
cup.ingredients.Add("Coffee powder");
cup.ingredients.Add("Hot water");
return cup;
}
}
Il nostro metodo di test configura il contenitore per restituire CoffeeService
quando viene richiesta un'istanza di ICoffeeService
[Test]
public void CoffeeService_GetCoffee_ReturnsCupWithCoffeeAndHotWater()
{
// Arrange
var container = new SimpleInjector.Container();
container.Register<ICoffeeService, CoffeeService>();
// Act
var coffeeService = container.GetInstance<ICoffeeService>();
var cup = coffeeService.GetCoffee();
// Assert
Assert.That(cup.ingredients.Count == 2);
Assert.That(cup.ingredients.Contains("Coffee powder"));
Assert.That(cup.ingredients.Contains("Hot water"));
}
Decidiamo di dover aggiungere sempre crema al nostro caffè, ma invece di cambiare CoffeeService
invece Decorare con la nuova funzione ( principio Aperto / Chiuso ).
Il decoratore
public class AddCreamDecorator : ICoffeeService
{
private readonly ICoffeeService decorated;
public AddCreamDecorator(ICoffeeService decorated)
{
this.decorated = decorated;
}
public Cup GetCoffee()
{
Cup cup = this.decorated.GetCoffee();
cup.ingredients.Add("Cream");
return cup;
}
}
E il nuovo test
[Test]
public void DecoratedCoffeeService_GetCoffee_CupWithCoffeeAndHotWaterAndCream()
{
// Arrange
var container = new SimpleInjector.Container();
container.Register<ICoffeeService, CoffeeService>();
container.RegisterDecorator(typeof(ICoffeeService), typeof(AddCreamDecorator));
// Act
var coffeeService = container.GetInstance<ICoffeeService>();
var cup = coffeeService.GetCoffee();
// Assert
Assert.That(cup.ingredients.Count == 3);
Assert.That(cup.ingredients.Contains("Coffee powder"));
Assert.That(cup.ingredients.Contains("Hot water"));
Assert.That(cup.ingredients.Contains("Cream"));
}
Il punto più importante da prendere da questo esempio è che eseguiamo il codice e compiliamo il tipo di servizio ICoffeeService
e il contenitore ci fornisce l'implementazione di runtime configurata. Nel test uno otteniamo un'istanza di CoffeeService
mentre nel secondo test otteniamo un'istanza di CoffeeService
decorata con un'istanza di AddCreamDecorator
- in entrambi i casi il chiamante non si cura di ottenere il servizio di cui ha bisogno .