Utilizzare l'iniezione delle dipendenze per gli oggetti dati?

11

Sto solo imparando l'iniezione di dipendenza e sono bloccato su qualcosa. Dipendenza Iniezione raccomanda di inviare classi dipendenti attraverso il costruttore, ma mi chiedo se sia necessario per gli oggetti dati. Dal momento che la Testabilità delle unità è uno dei principali vantaggi di DI, un oggetto dati, che memorizza solo i dati, e non tutte le procedure sono mai testate in unità, rendendo DI un livello non necessario di complessità, o aiuta ancora a mostrare dipendenze anche con oggetti dati?

Class DO{
    DO(){
        DataObject2List = new List<DO2>();
    }

    public string Field1;
    public string Field2;
    public List<DO2> DataObject2List;
}

Class DO2{
    public DateTime Date;
    public double Value;
}
    
posta sooprise 10.06.2011 - 19:42
fonte

4 risposte

7

Vorrei suggerire un chiarimento su alcuni dei termini che stai usando qui, in particolare "dipendenza" e "iniezione di dipendenza".

Dipendenza:

Una "dipendenza" è in genere un oggetto complesso che esegue alcune funzionalità di cui un'altra classe potrebbe aver bisogno di dipendere. Alcuni esempi classici potrebbero essere un logger o un accessor di database o alcuni componenti che elaborano una particolare logica aziendale.

Un oggetto solo dati come un DTO o un oggetto valore non sono in genere definiti come "dipendenza", poiché non eseguono alcuna funzione necessaria.

Dopo averlo guardato in questo modo, quello che stai facendo nel tuo esempio ( comporre l'oggetto DO con un elenco di oggetti D02 attraverso il costruttore) non dovrebbe essere considerato "dipendenza" iniezione "a tutti. Sta solo impostando una proprietà. Sta a te decidere se fornirlo nel costruttore o in un altro modo, ma semplicemente passarlo attraverso il costruttore non lo rende un'iniezione di dipendenza.

Iniezione di dipendenza:

Se la tua classe DO2 forniva effettivamente alcune funzionalità aggiuntive necessarie per la classe DO , allora sarebbe davvero una dipendenza. In tal caso, la classe dipendente, DO , dovrebbe dipendere da un'interfaccia (come ILogger o IDataAccessor), e a sua volta fare affidamento sul codice chiamante per fornire quell'interfaccia (in altre parole, per "inserirla" in DO istanza).

Iniettare la dipendenza in questo modo rende l'oggetto DO più flessibile, poiché ogni diverso contesto può fornire la propria implementazione dell'interfaccia all'oggetto DO . (Pensa al test delle unità.)

    
risposta data 11.06.2011 - 00:52
fonte
5

Farò del mio meglio per ridurre la confusione nella domanda.

Prima di tutto, "Oggetto dati" non è un termine significativo. Se la caratteristica di definizione di solo di questo oggetto è che non ha metodi, allora non dovrebbe esistere affatto . Un utile oggetto senza comportamento deve essere contenuto in almeno una delle seguenti sottocategorie:

  • Oggetti valore o "record" non hanno alcuna identità. Dovrebbero essere di valore tipi , con semantica copy-on-reference, assumendo che l'ambiente lo supporti. Poiché queste sono strutture fisse, un VO dovrebbe sempre essere solo un tipo primitivo o una sequenza fissa di primitive. Pertanto, un VO non dovrebbe avere alcuna associazione di dipendenza o ; qualsiasi costruttore non predefinito esisterebbe unicamente allo scopo di inizializzare il valore, cioè perché non può essere espresso come letterale.

  • Gli oggetti di trasferimento dei dati sono spesso erroneamente confusi con oggetti valore. I DTO fanno hanno identità, o almeno possono . L'unico scopo di un DTO è facilitare il flusso di informazioni da un dominio all'altro. Non hanno mai "dipendenze". Loro possono avere associazioni (vale a dire un array o una collezione) ma la maggior parte delle persone preferisce renderle piatte. Fondamentalmente, sono analoghi alle righe nell'output di una query di database; sono oggetti transitori che di solito devono essere persistenti o serializzati, e quindi non possono fare riferimento a qualsiasi tipo di astrazione, in quanto ciò li renderebbe inutilizzabili.

  • Infine, Oggetti di accesso ai dati forniscono un involucro o facciata a un database di qualche tipo. Ovviamente hanno dipendenze: dipendono dalla connessione al database e / o dai componenti di persistenza. Tuttavia, le loro dipendenze sono quasi sempre gestite esternamente e totalmente invisibili ai chiamanti. Nel modello Active Record è il framework che gestisce tutto tramite la configurazione; negli antichi modelli DAO (antichi per gli standard odierni) è possibile realizzarli solo tramite il contenitore. Se vedessi uno di questi con l'iniezione del costruttore, sarei molto, molto preoccupato.

Potresti anche pensare a un oggetto entità o "oggetto business" , e in questo caso do vuoi supportare l'iniezione delle dipendenze, ma non nel modo in cui pensi o per i motivi che pensi. Non è a vantaggio di codice utente , è a beneficio di un gestore di entità o di un ORM, che inietterà silenziosamente un proxy che intercetta per fare cose di fantasia come la comprensione delle query o caricamento lento.

In questi, di solito non fornisce un costruttore per l'iniezione; invece, devi solo rendere virtuale la proprietà e utilizzare un tipo astratto (ad esempio IList<T> anziché List<T> ). Il resto accade dietro le quinte e nessuno è più saggio.

Quindi, tutto sommato, direi che un pattern DI visibile applicato a un "oggetto dati" non è necessario e probabilmente anche una bandiera rossa; ma in gran parte, perché la stessa esistenza dell'oggetto è una bandiera rossa, tranne nel caso in cui sia specificatamente utilizzata per rappresentare i dati da un database. In quasi tutti gli altri casi si tratta di un odore di codice, in genere l'inizio di un Modello di dominio anemico o almeno un Poltergeist .

Per ripetere:

  1. Non creare "oggetti dati".
  2. Se devi creare un "oggetto dati", assicurati che abbia uno scopo chiaramente definito . Questo scopo ti dirà se DI è appropriato o meno. È impossibile prendere decisioni significative sul design di un oggetto che non dovrebbe esistere in primo luogo.

Fin.

    
risposta data 10.06.2011 - 22:55
fonte
0

Nel tuo esempio, DO non ha dipendenze funzionali (fondamentalmente perché non fa nulla). Ha una dipendenza dal tipo concreto DO2 , quindi potresti voler introdurre un'interfaccia per estrarre DO2 , in modo che il consumatore possa implementare la propria implementazione concreta della classe figlio.

Davvero, quale dipendenza ti inietterebbe qui?

    
risposta data 10.06.2011 - 20:40
fonte
0

Poiché questo è un oggetto dati nel livello di accesso ai dati, dovrebbe dipendere direttamente da un servizio di database. È possibile specificare un DatabaseService per il costruttore:

DataObject dataObject = new DataObject(new DatabaseService());
dataObject.Update();

Ma l'iniezione non deve essere nel costruttore. In alternativa, è possibile fornire la dipendenza tramite ciascun metodo CRUD. Preferisco questo metodo al precedente perché il tuo oggetto dati non ha bisogno di sapere dove si manterrà finché non avrai effettivamente bisogno di persisterlo.

DataObject dataObject = new DataObject();
dataObject.Update(new DatabaseService());

Sicuramente non vuoi nascondere la costruzione nei metodi CRUD!

public void Update()
{
    // DON'T DO THIS!
    using (DatabaseService dbService = new DatabaseService())
    {
        ...
    }
}

Un'opzione alternativa sarebbe costruire il DatabaseService tramite un metodo di classe sovrascrivibile.

public void Update()
{
    // GetDatabaseService() is protected virtual, so in unit testing
    // you can subclass the Data Object and return your own
    // MockDatabaseService.
    using (DatabaseService dbService = GetDatabaseService())
    {
        ...
    }
}

Un'alternativa finale è usare un ServiceLocator in stile singleton. Anche se non mi piace questa opzione, è unità testabile.

public void Update()
{
    // The ServiceLocator would not be a real singleton. It would have a setter
    // property so that unit tests can swap it out with a mock implementation
    // for unit tests.
    using (DatabaseService dbService = ServiceLocator.GetDatabaseService())
    {
        ...
    }
}
    
risposta data 10.06.2011 - 20:57
fonte

Leggi altre domande sui tag