-
Who is going to determine what classes (that implement
SwitchableDevice
) need to be called?
-
Who tells
Button
what devices he need to turn on/off?
-
How do you tell an object that uses something abstract which concrete things it needs to use? (Please correct me if this question is completely wrong)
Il termine spesso usato è "cablaggio" , che si riferisce all'attività di collegamento delle classi disaccoppiate. In genere implica la connessione di interfacce astratte a classi concrete e la gestione dell'iniezione di ogni istanza concreta all'avvio dell'applicazione.
Il cablaggio si verifica comunemente in una parte di livello superiore di un programma vicino al punto di ingresso noto come Root di composizione . Questo potrebbe essere un metodo chiamato da main()
o potrebbe anche essere main()
stesso. (Questo non è sempre il caso però, ad esempio, ASP.NET gestisce il cablaggio su base per-HTTP-request, quindi questo è più sui casi d'uso tipici che sulle regole hard / fast)
L'analogia potrebbe essere simile a cablare un sistema elettronico che ha molti piccoli componenti che devono essere collegati insieme per creare un sistema funzionante; per esempio. collegare i molti componenti di un robot che ha tutti i tipi di parti mobili e circuiti stampati.
La motivazione di questo approccio alla costruzione del software è quella di essere in grado di trattare un'applicazione come un insieme di "elementi costitutivi" indipendenti, scollegati e coesi che sono fusi insieme al livello più alto dell'applicazione al fine di creare qualcosa che funziona.
Ogni elemento costitutivo di un'applicazione può rappresentare (o assumere la responsabilità per) un aspetto delle funzionalità dell'applicazione. Ad esempio, un repository di oggetti, una factory, un'interfaccia a un sistema di messaggistica di rete, un motore di regole aziendali, un sistema di elaborazione degli eventi, un'interfaccia a un'API esterna, un'interfaccia utente "menu principale", ecc.
La divisione di classi / componenti in questo modo consente a tutti i componenti di essere sviluppati autonomamente, con un proprio ciclo di sviluppo indipendente, i propri test di unità e la propria identità; agnostico ad altri componenti dell'applicazione.
Molte applicazioni hanno qualche tipo di componente di tipo shell o controller che ospita l'interfaccia utente (app GUI) o il thread principale (app di servizio / console) che collega il ' gap 'tra UI / UX e il resto dei componenti dell'app.
Rompere il comportamento di un'applicazione in molte classi più piccole spesso porta a un certo numero di istanza singola , (soprattutto) oggetti senza stato la cui durata è uguale a quella dell'applicazione, ma che devono comunque interagire tra loro; questi tipi di classi sono candidati ideali per l'iniezione di dipendenza.
Ci sono molti casi in cui il comportamento applicativo può derivare anche da oggetti usa e getta, di breve durata, a volte anche questi vengono iniettati, ma possono essere un po 'più difficili da gestire, quindi a volte vengono incapsulati all'interno di altri modelli come Strategia, Fabbrica, ecc.
Un codice c # molto semplice per l'esempio di Bob Martin potrebbe essere simile a:
static void Main(string[] args)
{
ISwitchableDevice switchableDevice = new Lamp();
var button = new Button(switchableDevice);
// TODO - wiring for other classes which use button and switchableDevice
}
Non penso che l'esempio di Uncle Bob su proprio è veramente sufficiente per dimostrare DI / IoC in un'applicazione reale - dimostra solo un'iniezione del costruttore come alternativa alla proprietà dell'oggetto.
Ecco un potenziale esempio molto elaborato di un servizio in background la cui responsabilità è quella di ascoltare i messaggi di controllo della rete e attivare e disattivare una lampada:
static void Main(string[] args)
{
ISwitchableDevice switchableDevice = new Lamp();
var button = new Button(switchableDevice);
var messageListener = new NetworkMessageListener();
var serviceController = new ServiceController(messageListener, button);
// The wiring is done, now start the main thread and block...
serviceController.StartMainThread();
Thread.Wait();
}
Nell'esempio sopra, suppongo che l'app sia composta da 4 diverse classi, tutte mostrate nel metodo Main
; quelle classi non hanno bisogno di chiamare new
per creare altre classi perché Main()
(la radice di composizione ) usa DI DI dei poveri per creare i vari componenti del applicazione e collegarli tutti insieme.
Si può presumere che ogni costruttore di classi concrete accetti le sue dipendenze usando un po 'di interface
, e che nessuna di queste classi sia effettivamente a conoscenza l'una dell'altra.
Lo schema sopra si riduce molto bene - quando un nuovo componente deve essere aggiunto all'applicazione, viene aggiunto alla radice di composizione e la radice di composizione gestisce i suoi collegamenti / dipendenze (e / o lo inietta in uno dei componenti esistenti, se necessario).