Utiliser l'injection de dépendances pour les objets de données?

11

J'apprends juste sur l'injection de dépendance et je suis coincé sur quelque chose. L'injection de dépendance recommande d'envoyer des classes dépendantes via le constructeur, mais je me demande si cela est nécessaire pour les objets de données. Étant donné que la testabilité unitaire est l'un des principaux avantages de DI, un objet de données, qui ne stocke que des données, et aucune procédure ne sera jamais testée unitaire, faisant de DI une couche de complexité inutile, ou aide-t-il toujours à montrer les dépendances même avec des objets de données?

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;
}
sooprise
la source
Qu'entendez-vous exactement par «objet de données»? Ce n'est pas un terme standard. Parlez-vous d'un DTO ou faites-vous référence à n'importe quelle classe sans méthodes (comme une partie particulièrement ennuyeuse d'un modèle de domaine)? Il y a une énorme différence entre les deux.
Aaronaught
Bien sûr, je veux dire juste une classe sans méthodes, une classe qui stocke juste des données. Si Data Object n'est pas le terme correct pour cela, en existe-t-il un ou est-il simplement appelé une classe sans méthodes?
sooprise le
@sooprise C'est une bonne question. Bien pensé.
Matthew Rodatus
@sooprise Peut-être que ma réponse est hors sujet. Où allez-vous mettre les méthodes CRUD? Dans une classe d'accès aux données distincte qui prendrait les objets de données et les conserverait dans une table de base de données? C'est-à-dire DataAccess.Create (<DataObject>)?
Matthew Rodatus
1
@Matthew, ce serait un objet d' accès aux données - si c'est en fait ce dont parle l'OP, ce n'est pas clair du tout. Les implémentations modernes ont de toute façon tendance à s'éloigner de ce modèle, en s'appuyant sur des référentiels et / ou des unités de travail.
Aaronaught

Réponses:

7

Je voudrais suggérer une clarification de la terminologie que vous utilisez ici, en particulier "dépendance" et "injection de dépendance".

Dépendance:

Une "dépendance" est généralement un objet complexe qui exécute certaines fonctionnalités dont une autre classe peut avoir besoin. Quelques exemples classiques seraient un enregistreur ou un accesseur de base de données ou un composant qui traite un élément particulier de la logique métier.

Un objet de données uniquement comme un DTO ou un objet de valeur n'est généralement pas appelé une "dépendance", car ils n'exécutent pas certaines fonctions nécessaires.

Une fois que vous le regardez de cette façon, ce que vous faites dans votre exemple ( composer l' DOobjet avec une liste d' D02objets via le constructeur) ne doit pas du tout être considéré comme une "injection de dépendance". Il s'agit simplement de définir une propriété. C'est à vous de décider si vous le fournissez dans le constructeur ou d'une autre manière, mais le simple fait de le transmettre via le constructeur ne fait pas de l'injection de dépendances.

Injection de dépendance:

Si votre DO2classe fournissait en fait des fonctionnalités supplémentaires dont la DOclasse a besoin, ce serait vraiment une dépendance. Dans ce cas, la classe dépendante DO,, devrait dépendre d'une interface (comme ILogger ou IDataAccessor), et à son tour s'appuyer sur le code appelant pour fournir cette interface (en d'autres termes, pour l'injecter dans l' DOinstance).

L'injection de la dépendance de cette manière rend l' DOobjet plus flexible, car chaque contexte différent peut fournir sa propre implémentation de l'interface à l' DOobjet. (Pensez aux tests unitaires.)

Eric King
la source
7

Je vais faire de mon mieux pour éliminer la confusion dans la question.

Tout d'abord, "Data Object" n'est pas un terme significatif. Si la seule caractéristique qui définit cet objet est qu'il n'a pas de méthodes, alors il ne devrait pas exister du tout . Un objet utile sans comportement doit tenir dans au moins une des sous-catégories suivantes:

  • Les objets de valeur ou «enregistrements» n'ont aucune identité. Il doit s'agir de types de valeurs , avec une sémantique de copie sur référence, en supposant que l'environnement le prend en charge. Puisqu'il s'agit de structures fixes, une VO ne doit être qu'un type primitif ou une séquence fixe de primitives. Par conséquent, une VO ne devrait pas avoir de dépendances ou d' associations; tout constructeur non par défaut existerait uniquement dans le but d'initialiser la valeur, c'est-à-dire parce qu'elle ne peut pas être exprimée comme un littéral.

  • Les objets de transfert de données sont souvent confondus par erreur avec des objets de valeur. DTO faire avoir des identités, ou du moins ils le peuvent . Le seul objectif d'un DTO est de faciliter la circulation des informations d'un domaine à l'autre. Ils n'ont jamais de "dépendances". Ils peuvent avoir des associations (c'est-à-dire à un tableau ou à une collection) mais la plupart des gens préfèrent les rendre plats. Fondamentalement, ils sont analogues aux lignes de la sortie d'une requête de base de données; ce sont des objets transitoires qui doivent généralement être persistés ou sérialisés, et ne peuvent donc pas référencer de types abstraits, car cela les rendrait inutilisables.

  • Enfin, les objets d'accès aux données fournissent un wrapper ou une façade à une base de données quelconque. Ceux-ci ont évidemment des dépendances - ils dépendent de la connexion à la base de données et / ou des composants de persistance. Cependant, leurs dépendances sont presque toujours gérées en externe et totalement invisibles pour les appelants. Dans le modèle Active Record , c'est le cadre qui gère tout à travers la configuration; dans les modèles DAO plus anciens (anciens selon les normes actuelles), vous ne pouviez les construire que via le conteneur. Si j'en voyais un avec l'injection de constructeur, je serais très, très inquiet.

Vous pouvez également penser à un objet entité ou « objet métier » , et dans ce cas , vous ne voulez soutenir l' injection de dépendance, mais pas de la manière que vous pensez ou pour les raisons que vous pensez. Ce n'est pas au profit du code utilisateur , c'est au profit d'un gestionnaire d'entités ou ORM, qui injectera silencieusement un proxy qu'il intercepte pour faire des choses fantaisistes comme la compréhension des requêtes ou le chargement paresseux.

Dans ceux-ci, vous ne fournissez généralement pas de constructeur pour l'injection; au lieu de cela, il vous suffit de rendre la propriété virtuelle et d'utiliser un type abstrait (par exemple IList<T>au lieu de List<T>). Le reste se passe dans les coulisses, et personne n'est plus sage.

Donc, dans l'ensemble, je dirais qu'un modèle DI visible appliqué à un "objet de données" est inutile et probablement même un drapeau rouge; mais en grande partie, c'est parce que l' existence même de l'objet est un drapeau rouge, sauf dans le cas où il est spécifiquement utilisé pour représenter des données d'une base de données. Dans presque tous les autres cas, c'est une odeur de code, généralement les débuts d'un modèle de domaine anémique ou à tout le moins un Poltergeist .

Recommencer:

  1. Ne créez pas "d'objets de données".
  2. Si vous devez créer un "objet de données", assurez-vous qu'il a un objectif clairement défini . Cet objectif vous dira si DI est approprié ou non. Il est impossible de prendre des décisions de conception significatives sur un objet qui ne devrait pas exister en premier lieu.

Ailette.

Aaronaught
la source
0

Dans votre exemple, DOn'a pas de dépendances fonctionnelles (essentiellement parce qu'il ne fait rien). Il dépend du type concret DO2, vous voudrez peut-être introduire une interface pour l'abstrait DO2, afin que le consommateur puisse implémenter sa propre implémentation concrète de la classe enfant.

Vraiment, quelle dépendance injecteriez-vous ici?

Scott Whitlock
la source
Par une autre question à laquelle j'ai répondu, je pense que la question se réfère à un objet de données avec des opérations CRUD incorporées. Comment / où la dépendance de la base de données est-elle injectée dans l'objet de données? Dans les méthodes? Chez le constructeur? D'une autre manière? L'hypothèse est que la dépendance ne doit pas être cachée dans le corps des méthodes, ce qui désactive la séparation de la dépendance de la base de données de l'objet de données afin que l'objet de données puisse être testé unitaire.
Matthew Rodatus
@Matthew Rodatus - Je vois. Oui, dans ce cas, vous avez deux choix: injecter le service de persistance ou créer une autre classe appelée DOPersisterqui sait persister DOet le laisser comme un objet strictement data-only (mieux à mon avis). Dans ce dernier cas, DOPersisterserait injecté avec la dépendance de la base de données.
Scott Whitlock
Après avoir relu sa question, je suis moins sûr. Mon analyse peut être erronée. Il a dit dans sa question que son DO n'aurait aucune procédure. Cela signifierait que la persistance ne se produit PAS dans le DO. Dans ce cas, votre réponse est juste - il n'y a pas de dépendances à injecter.
Matthew Rodatus
0

Comme il s'agit d'un objet de données dans la couche d'accès aux données, il doit dépendre directement d'un service de base de données. Vous pouvez spécifier un DatabaseService au constructeur:

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

Mais, l'injection n'a pas besoin d'être dans le constructeur. Alternativement, vous pouvez fournir la dépendance via chaque méthode CRUD. Je préfère cette méthode à la précédente car votre objet de données n'a pas besoin de savoir où il persistera jusqu'à ce que vous ayez réellement besoin de le conserver.

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

Vous ne voulez certainement pas cacher la construction dans les méthodes CRUD!

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

Une autre option serait de construire le DatabaseService via une méthode de classe surchargeable.

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())
    {
        ...
    }
}

Une dernière alternative consiste à utiliser un ServiceLocator de style singleton. Bien que je n'aime pas cette option, elle est testable à l'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())
    {
        ...
    }
}
Matthew Rodatus
la source