Chiarimento sul principio dell'inversione di dipendenza

4

Sto leggendo il libro "Agile Software Development, Principles, Patterns and Practices" di Robert C. Martin .

Quando parla del principio di inversione delle dipendenze dà il seguente esempio di violazione DIP:

Questomisembramoltochiaro,poichél'oggettodilivellosuperioreButtondipendedaunoggettodilivelloinferioreLamp.

Lasoluzioneconcuiarrivaè:

Crea un'interfaccia in modo che Button non dipenda più dall'oggetto Lamp .

La teoria mi sembra molto chiara, tuttavia non riesco a spiegarmi come usare il principio nei progetti di vita reale.

  • Chi sta per determinare quali classi (che implementano SwitchableDevice ) devono essere chiamate?

  • Chi indica a Button quali dispositivi ha bisogno di attivare / disattivare?

  • In che modo dici a un oggetto che utilizza qualcosa di astratto che concreto ha bisogno di usare? (Per favore correggimi se questa domanda è completamente sbagliata)

Se qualcosa non è chiaro sulla mia domanda, faccelo sapere, sarò lieto di chiarire le cose per te.

    
posta Melvin Koopmans 13.04.2017 - 20:08
fonte

3 risposte

4
  • 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).

    
risposta data 13.04.2017 - 21:16
fonte
0

Ci sono due parti per ogni progetto di programmazione:

  • le parti divertenti - questo è dove si programma la logica, dove si creano dipendenze iniettabili usando l'iniezione di dipendenza,
  • le parti necessarie - dove viene creato il grafico dell'oggetto 1 .

Nel tuo caso hai le parti divertenti nell'accensione e spegnimento di alcuni dispositivi: definisci una logica che quando un pulsante viene attivato, un dispositivo si accende o si spegne. Quello che ti manca è la parte necessaria, che indica quale pulsante verrà effettivamente assegnato a quale dispositivo.

Chi?

Beh, questo sarà sempre te, come programmatore. Stai fornendo la logica di cablaggio, stai dicendo, come viene creato l'oggetto Button . Puoi forzatamente istanziarlo direttamente nel codice o dare agli utenti una certa libertà. Per esempio. avendo un List di SwitchableDevice oggetti. E quando un utente seleziona un dispositivo in questo elenco, costruisci un pulsante iniettandogli l'istanza SwitchableDevice nell'indice selezionato dall'elenco.

Come?

Dipende da cosa è una vera classe.

Se si tratta di un servizio che dovrebbe funzionare come un singleton, è probabile che tu usi un framework di dipendenze, come Spring, Guice, Ninject, PicoContainer.NET, ... dove configuri il framework per fornirti un concreto istanza quando un'interfaccia è richiesta da un'altra classe. Questo di solito accade quando sai che una volta che un'applicazione si avvia, avrai sempre e solo bisogno di un'implementazione di qualche interfaccia.

Se d'altra parte hai bisogno di una commutazione di runtime (offri agli utenti qualche libertà di configurazione), dovrai fornire qualche combinazione di pattern factory / strategia, dove avrai un metodo che ti restituirà un'istanza specifica di un'interfaccia, la maggior parte probabilmente basato su un flag, come l'id del tipo.

public SwitchableDevice get(SwitchableDevice.Types type)
{
    switch (type) {
        case SwitchableDevice.Types.Radio:
            return new Radio();
        case SwitchableDevice.Types.Light:
            return new Light();
        case SwitchableDevice.Types.Computer:
            return new Computer();
        default:
            throw new UnimplementedException("Unsupported type.");
    }
}

Questa è una forma molto semplificata, nella vita reale è probabile che un Radio , un Light e / o un oggetto Computer esisteranno già da qualche altra parte, siano presenti in una memoria e quando un utente sceglie un dispositivo specifico lo recuperi, sapendo che implementa l'interfaccia SwitchableDevice e lo collega a un pulsante di tua scelta.

1 In che modo le classi sono cablate (istanziate) insieme - quali istanze sono effettivamente iniettate.

    
risposta data 13.04.2017 - 21:16
fonte
0

La comunicazione effettiva potrebbe essere eseguita tramite un pattern di osservatore. Diciamo che ho un controller di periferica commutabile che gestisce un elenco di oggetti che implementano ISwitchableDevice. Inoltre è stato sottoscritto all'evento click del pulsante.

Quindi, quando si fa clic sul pulsante (evento generato), il controller chiama ciascuno degli oggetti che sta attualmente mantenendo attivo il metodo di attivazione / disattivazione.

    
risposta data 13.04.2017 - 21:17
fonte