Utilizzare l'iniezione di dipendenza per gli oggetti dati?


11

Sto solo imparando l'iniezione di dipendenza e sono bloccato su qualcosa. Dependency Injection consiglia di inviare classi dipendenti tramite il costruttore, ma mi chiedo se ciò sia necessario per gli oggetti dati. Poiché l'Unità-Testabilità è uno dei principali vantaggi di DI, sarebbe un oggetto dati, che archivia solo i dati, e che nessuna procedura venga mai testata dall'unità, rendendo DI uno strato di complessità non necessario o aiuta comunque a mostrare anche le dipendenze 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;
}

Cosa intendi esattamente con "oggetto dati"? Questo non è un termine standard. Stai parlando di un DTO o ti riferisci a qualsiasi classe senza metodi (come una parte particolarmente noiosa di un modello di dominio)? C'è un'enorme differenza tra i due.
Aaronaught il

Certo, intendo solo una classe senza metodi, una classe che memorizza solo i dati. Se Data Object non è il termine corretto per questo, ce n'è uno o è semplicemente chiamato una classe senza metodi?
sooprise,

@sooprise Questa è una buona domanda. Pensare bene.
Matthew Rodatus,

@sooprise Forse la mia risposta è fuori base. Dove inserirai i metodi CRUD? In una classe di accesso ai dati separata che prenderebbe gli oggetti dati e li persisterebbe in una tabella di database? Vale a dire DataAccess.Create (<DataObject>)?
Matthew Rodatus,

1
@Matthew, questo sarebbe un oggetto di accesso ai dati - se questo è in realtà ciò di cui parla l'OP, allora non è affatto chiaro. Le implementazioni moderne tendono comunque ad allontanarsi da questo modello, basandosi su repository e / o unità di lavoro.
Aaronaught,

Risposte:


7

Vorrei suggerire un chiarimento di alcuni dei termini utilizzati qui, in particolare "dipendenza" e "iniezione di dipendenza".

Dipendenza:

Una "dipendenza" è in genere un oggetto complesso che esegue alcune funzionalità dalle quali potrebbe essere necessario dipendere da un'altra classe. Alcuni esempi classici potrebbero essere un logger o un programma di accesso al database o un componente che elabora una particolare logica di business.

Un oggetto solo dati come un DTO o un oggetto valore non viene in genere indicato come "dipendenza", poiché non svolgono alcune funzioni necessarie.

Una volta che lo guardi in questo modo, ciò che stai facendo nel tuo esempio ( comporre l' DOoggetto con un elenco di D02oggetti attraverso il costruttore) non dovrebbe essere considerato affatto "iniezione di dipendenza". Sta solo impostando una proprietà. Sta a te se lo fornisci nel costruttore o in qualche altro modo, ma semplicemente passarlo attraverso il costruttore non lo rende iniezione di dipendenza.

Iniezione di dipendenza:

Se la tua DO2classe fornisse effettivamente alcune funzionalità aggiuntive di cui la DOclasse ha bisogno, 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 tale interfaccia (in altre parole, per "iniettarlo" DOnell'istanza).

L'iniezione della dipendenza in questo modo rende l' DOoggetto più flessibile, poiché ogni diverso contesto può fornire la propria implementazione dell'interfaccia DOsull'oggetto. (Pensa al test unitario.)


7

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

Prima di tutto, "Oggetto dati" non è un termine significativo. Se l' unica caratteristica che definisce questo oggetto è che non ha metodi, allora non dovrebbe esistere affatto . Un oggetto utile senza comportamento dovrebbe rientrare in almeno una delle seguenti sottocategorie:

  • Value Objects o "records" non hanno alcuna identità. Dovrebbero essere tipi di valore , con semantica di copia su riferimento, supponendo che l'ambiente lo supporti. Poiché si tratta di strutture fisse, un VO dovrebbe essere sempre e solo un tipo primitivo o una sequenza fissa di primitivi. Pertanto, un VO non dovrebbe avere dipendenze o associazioni; qualsiasi costruttore non predefinito esisterebbe esclusivamente allo scopo di inizializzare il valore, cioè perché non può essere espresso come letterale.

  • Gli oggetti di trasferimento dati vengono spesso confusi erroneamente con oggetti valore. DTOs fare hanno identità, o almeno possibile . L'unico scopo di un DTO è di facilitare il flusso di informazioni da un dominio all'altro. Non hanno mai "dipendenze". Essi possono avere le associazioni (cioè di un array o di raccolta), ma la maggior parte delle persone preferisce farli piatta. Fondamentalmente, sono analoghi alle righe nell'output di una query del database; sono oggetti transitori che di solito devono essere persistenti o serializzati e pertanto non possono fare riferimento a nessun tipo astratto, poiché ciò li renderebbe inutilizzabili.

  • Infine, Data Access Objects fornisce un wrapper o una facciata a un database di qualche tipo. Questi 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 attraverso la configurazione; nei vecchi modelli DAO (antichi per gli standard odierni) si potevano costruire solo attraverso il container. Se vedessi uno di questi con l'iniezione del costruttore, sarei molto, molto preoccupato.

Si può anche pensare di un oggetto entità o "oggetto di business" , e in questo caso si fa vuole iniezione di sostegno dipendenza, ma non nel modo in cui si pensa o per le ragioni che si pensa. Non è a vantaggio del codice utente , è a vantaggio di un gestore di entità o ORM, che inietterà silenziosamente un proxy che intercetta per fare cose fantasiose come la comprensione delle query o il caricamento lazy.

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

Quindi, nel complesso, direi che un modello DI visibile applicato a un "oggetto dati" non è necessario e probabilmente anche una bandiera rossa; ma in gran parte, ciò è dovuto al fatto che l' esistenza stessa dell'oggetto è una bandiera rossa, tranne nel caso in cui sia specificamente utilizzato per rappresentare 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 .

Reiterare:

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

Fin.


0

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

Davvero, quale dipendenza vorresti iniettare qui?


Per un'altra sua domanda a cui ho risposto, penso che la domanda si riferisca a un oggetto dati con operazioni CRUD incorporate. Come / dove viene iniettata la dipendenza del database nell'oggetto dati? Nei metodi? Nel costruttore? In qualche altro modo? Il presupposto è che la dipendenza non dovrebbe essere nascosta nel corpo dei metodi, il che disabilita la separazione della dipendenza del database dall'oggetto dati in modo che l'oggetto dati possa essere testato dall'unità.
Matthew Rodatus,

@Matthew Rodatus - Capisco. Sì, in quel caso, hai due possibilità: iniettare il servizio di persistenza o creare un'altra classe chiamata DOPersisterche sappia come persistere DOe lasciarla come un oggetto strettamente di soli dati (meglio secondo me). In quest'ultimo caso, DOPersisterverrebbe iniettato con la dipendenza del database.
Scott Whitlock,

Dopo aver riletto la sua domanda, ne sono meno sicuro. La mia analisi potrebbe essere sbagliata. Nella sua domanda ha detto che il suo DO non avrebbe alcuna procedura. Ciò significherebbe che la persistenza NON si verifica nel DO. In tal caso, la tua risposta è giusta: non ci sono dipendenze da iniettare.
Matthew Rodatus,

0

Poiché si tratta di 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 persisterà fino a quando non dovrai effettivamente persistere.

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 quella di costruire DatabaseService tramite un metodo di classe sostituibile.

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'ultima alternativa è utilizzare un ServiceLocator in stile singleton. Sebbene questa opzione non mi piaccia, è testabile in unità.

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())
    {
        ...
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.