2015-12-04 10 views
5

Sto lavorando su un problema di DI che penso di aver capito la causa, ma ho bisogno di alcuni suggerimenti per aggirare.Iniezione di dipendenza tra assiemi e spazi dei nomi

Ho creato un assembly autonomo che comunica con Sql (chiama questo assembly a) e un altro assembly che contiene la business logic (chiamare questo assembly b). Ho creato un'interfaccia per la classe db nell'assembly b. Poiché l'interfaccia non fa parte dell'assembly db, non ho bisogno di riferimenti al progetto db, e posso caricare un riferimento all'assembly db o a uno stub se voglio eseguire i test unitari in fase di esecuzione e nessuno dei due assembly ha bisogno di sapere dell'altro.

posso scrivere codice nella libreria logica di business che compila che assomiglia a questo: (finta che a e b sono spazi dei nomi nelle rispettive assemblee)

a.IDatabaseClass db_class = (a.IDatabase)new b.Database(); 

Quando provo a fare funzionare questo però, ottengo un'eccezione di cast non valida. Penso che compili perché l'interfaccia corrisponde perfettamente alla classe, ma fallisce in fase di esecuzione perché la firma dell'oggetto non vede IDatabase nella catena ereditaria della classe Database.

In C++ puoi scappare con qualsiasi cosa tu voglia, ma C# è un po 'più severo riguardo al cast di puntatori di oggetti. Anche se la classe ha tutte le firme delle funzioni corrette, esplode perché gli oggetti non corrispondono.

Ora potrei mettere l'interfaccia dell'oggetto db nell'assieme con l'oggetto db, ma la logica di business richiede un riferimento all'assembly db. Inoltre, questo crea solo complicazioni lungo la strada, perché se scrivo un oggetto db stub in un test di unità, ho bisogno di un riferimento all'assembly db solo per l'interfaccia che userò nel mio oggetto stub test. Questo non sembra essere districare accoppiamenti facendo questo ...

Potrei mettere tutte le interfacce in un terzo assembly che è un genitore per l'assembly db, la business logic e le unit test. Ecco come puoi risolvere i problemi di dipendenza circolare. Tuttavia, ciò lega l'assembly db all'assembly parent e lo rende molto meno modulare da utilizzare con altri progetti.

Sono aperto a suggerimenti su come posso configurare ciascun assieme in modo che funzionino in modo indipendente e possano essere utilizzati per DI. Suppongo che potrei tenere gli oggetti stub test nello stesso assembly del codice reale, ma sembra strano.

Soluzione: una delle risposte riportate di seguito rende il commento sul fatto che ciò per cui stavo girando è essenzialmente la digitazione delle anteprime per le interfacce. C# non supporta la digitazione anatra al momento, ma stavo pensando che potrebbe essere possibile dal momento che l'implementazione dell'interfaccia agisce in modo simile a quello che si potrebbe chiamare un puntatore di classe parziale (o probabilmente più precisamente, una raccolta di puntatori di funzione). Il mio esperimento mi stava mostrando il contrario, ed è per questo.

così fino a quando Redmond mette "altro germano reale" in C#, sembra che non possiamo raggiungere completamente l'ultimo livello elegante di disaccoppiamento degli assemblaggi.

+0

È possibile confermare che la libreria sql fa riferimento alla libreria della business logic? E che 'b.Database' implementa' a.IDatabase'? O le librerie sono strettamente indipendenti? – Rob

+1

Appare dopo aver letto di nuovo il post che sono librerie completamente indipendenti. Sicuramente opterei per l'approccio della "terza biblioteca". E 'sicuramente il più pulito, e non sono davvero d'accordo sul fatto che il riferimento a una libreria semplicemente esponendo le interfacce stia creando un accoppiamento stretto della libreria sql. – Rob

+0

Sei corretto, sono entrambi indipendenti (o potrebbero essere) indipendenti l'uno dall'altro. Speravo di essere in grado di usare DI per disconnettere completamente entrambi gli assembly dall'avere bisogno dell'altro, e sia un exe che un unit test condurranno i dati in fase di esecuzione. Sembra che i tentativi di C# per rendere il mio codice più sicuro con una forte digitazione mi impedisca di raggiungere questo ideale. – MadTigger

risposta

5

Creare una libreria di riferimento contenente le interfacce comuni. In questo modo avrai una fonte comune con tutta la tua logica agnostica di implementazione.

Non ho l'esperienza per sentirmi sicuro di fare una dichiarazione categorica, ma sospetto fortemente che l'accoppiamento sia principalmente un problema quando parli di singoli tipi che fanno riferimento a interfacce specifiche e come si comportano. Gli assembly non hanno lo stesso tipo di specificità che renderebbe l'accoppiamento un problema.

Modifica 1

Lasciami modificare ed estendere. Un gruppo deve fare riferimento all'altro, oppure entrambi devono fare riferimento a un assemblaggio comune. C# non ha interfacce anatra, se è una cosa.

Ora penso che la procedura migliore sia isolare la logica di business da interfacce esterne, quindi se si sta per fare qualcosa è necessario mantenere la logica aziendale isolata dall'implementazione del DB. Pertanto, l'assembly DB/Application fa riferimento alla logica aziendale, ma non viceversa.

Here's more information about the dependency orientation.

+0

Questa è probabilmente la descrizione più succinta di ciò che speravo di ottenere, "anatra-interfaccia". – MadTigger

+1

Ulteriori note sulla digitazione di C# duck: http://ericlippert.com/2014/01/02/what-is-duck-typing/ – MadTigger

3

Lascia che la libreria client (b) definisca l'interfaccia e faccia riferimento a tale libreria dalla libreria server (a).

Da Dependency Inversion Principle segue che "i client [...] possiedono le interfacce astratte" (APPP, capitolo 11), quindi non c'è motivo di mettere l'interfaccia in una terza libreria.

+0

E questo è esattamente quello che sto cercando di fare. Stavo cercando di essere intelligente e di farlo senza richiedere che l'uno o l'altro dipendesse dall'altro. Sospetto che non possa essere fatto in C# in quel modo. – MadTigger

+0

@MadTigger No, non è possibile farlo in C#. Non credo che saresti in grado di farlo anche in Java. –

+0

C'è un motivo per mettere l'interfaccia in un'altra terza libreria: Se c'è una relazione molti-a-molti: Alice fa un gioco Alice.Invaders (client), e mette un "negozio-plugin acquisti in-app" Alice. Acquista (implementazione). Il negozio dovrebbe essere sostituibile da altri negozi (ad esempio, collegare Bob.Shop). Tuttavia quei negozi dovrebbero essere inseribili in altri giochi. Non ha senso che Charlie.Pacman faccia riferimento alle interfacce in Alice.Invaders, quindi è necessario un terzo spazio dei nomi lis Alice.Shop.Interfaces. –