Ich lerne gerade etwas über Abhängigkeitsinjektion und bin auf etwas fixiert. Dependency Injection empfiehlt, abhängige Klassen über den Konstruktor zu senden, aber ich frage mich, ob dies für Datenobjekte erforderlich ist. Da die Unit-Testbarkeit einer der Hauptvorteile von DI ist, würde ein Datenobjekt, das nur Daten speichert, und keine Prozeduren jemals Unit-getestet werden, was DI zu einer unnötigen Komplexitätsebene macht, oder hilft es dennoch, Abhängigkeiten selbst anzuzeigen mit Datenobjekten?
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;
}
c#
dependency-injection
class-design
Sooprise
quelle
quelle
Antworten:
Ich möchte eine Klarstellung einiger der hier verwendeten Begriffe vorschlagen, insbesondere "Abhängigkeit" und "Abhängigkeitsinjektion".
Abhängigkeit:
Eine "Abhängigkeit" ist normalerweise ein komplexes Objekt, das einige Funktionen ausführt, von denen eine andere Klasse möglicherweise abhängen muss. Einige klassische Beispiele wären ein Logger oder ein Datenbank-Accessor oder eine Komponente, die eine bestimmte Geschäftslogik verarbeitet.
Ein Datenobjekt wie ein DTO oder ein Wertobjekt wird normalerweise nicht als "Abhängigkeit" bezeichnet, da sie keine erforderliche Funktion ausführen.
Wenn Sie es auf diese Weise sehen, was Sie in Ihrem Beispiel (tun Komponieren das
DO
Objekt mit einer Liste vonD02
Objekten durch den Konstruktor) nicht „dependecy Injektion“ in Betracht gezogen werden. Es wird nur eine Eigenschaft festgelegt. Es liegt an Ihnen, ob Sie es im Konstruktor oder auf andere Weise bereitstellen, aber wenn Sie es einfach durch den Konstruktor übergeben, wird es nicht zur Abhängigkeitsinjektion.Abhängigkeitsspritze:
Wenn Ihre
DO2
Klasse tatsächlich einige zusätzliche Funktionen bereitstellen würde, die dieDO
Klasse benötigt, wäre dies wirklich eine Abhängigkeit. In diesem Fall sollte die abhängige KlasseDO
von einer Schnittstelle (wie ILogger oder IDataAccessor) abhängen und sich wiederum auf den aufrufenden Code stützen, um diese Schnittstelle bereitzustellen (mit anderen Worten, um sie in dieDO
Instanz einzufügen ).Das Injizieren der Abhängigkeit auf diese Weise macht das
DO
Objekt flexibler, da jeder unterschiedliche Kontext seine eigene Implementierung der Schnittstelle zumDO
Objekt bereitstellen kann. (Denken Sie an Unit-Tests.)quelle
Ich werde mein Bestes geben, um die Verwirrung in der Frage zu beseitigen.
Erstens ist "Datenobjekt" kein aussagekräftiger Begriff. Wenn das einzige definierende Merkmal dieses Objekts ist, dass es keine Methoden hat, sollte es überhaupt nicht existieren . Ein nützliches verhaltensloses Objekt sollte in mindestens eine der folgenden Unterkategorien passen:
Wertobjekte oder "Datensätze" haben überhaupt keine Identität. Es sollten Werttypen mit Copy-on-Reference-Semantik sein, vorausgesetzt, die Umgebung unterstützt dies. Da es sich um feste Strukturen handelt, sollte ein VO immer nur ein primitiver Typ oder eine feste Folge von Primitiven sein. Daher sollte eine VO keine Abhängigkeiten oder Assoziationen haben. Jeder nicht standardmäßige Konstruktor würde nur zum Zweck der Initialisierung des Werts existieren, dh weil er nicht als Literal ausgedrückt werden kann.
Datenübertragungsobjekte werden häufig fälschlicherweise mit Wertobjekten verwechselt. DTOs tun Identitäten haben, oder zumindest sie können . Der einzige Zweck eines DTO besteht darin, den Informationsfluss von einer Domäne zur anderen zu erleichtern. Sie haben niemals "Abhängigkeiten". Sie können Assoziationen haben (dh zu einem Array oder einer Sammlung), aber die meisten Leute ziehen es vor, sie flach zu machen. Grundsätzlich sind sie analog zu Zeilen in der Ausgabe einer Datenbankabfrage. sie sind transiente Objekte , die in der Regel müssen beibehalten oder serialisiert werden und daher nicht können keine abstrakten Typen verweisen, da diese dadurch unbrauchbar machen würde.
Schließlich bieten Datenzugriffsobjekte einen Wrapper oder eine Fassade für eine Datenbank. Diese haben offensichtlich Abhängigkeiten - sie hängen von der Datenbankverbindung und / oder den Persistenzkomponenten ab. Ihre Abhängigkeiten werden jedoch fast immer extern verwaltet und sind für Anrufer völlig unsichtbar. Im Active Record- Muster ist es das Framework, das alles durch Konfiguration verwaltet. In älteren (nach heutigen Maßstäben alten) DAO-Modellen konnte man diese immer nur über den Container konstruieren. Wenn ich eines davon mit Konstruktorinjektion sehen würde, wäre ich sehr, sehr besorgt.
Möglicherweise denken Sie auch an ein Entitätsobjekt oder ein "Geschäftsobjekt" . In diesem Fall möchten Sie die Abhängigkeitsinjektion unterstützen, jedoch nicht so, wie Sie denken oder aus den Gründen, die Sie denken. Dies ist nicht zum Nutzen des Benutzercodes , sondern zum Nutzen eines Entity Managers oder ORM, der stillschweigend einen Proxy einfügt, den er abfängt, um ausgefallene Dinge wie das Verständnis von Abfragen oder das verzögerte Laden auszuführen.
In diesen Fällen geben Sie normalerweise keinen Konstruktor für die Injektion an. Stattdessen müssen Sie nur die Eigenschaft virtuell machen und einen abstrakten Typ verwenden (z . B.
IList<T>
anstelle vonList<T>
). Der Rest passiert hinter den Kulissen und niemand ist klüger.Alles in allem würde ich also sagen, dass ein sichtbares DI-Muster, das auf ein "Datenobjekt" angewendet wird, unnötig ist und wahrscheinlich sogar eine rote Fahne; Dies liegt jedoch zum großen Teil daran, dass die Existenz des Objekts eine rote Fahne ist, außer in dem Fall, in dem es speziell zur Darstellung von Daten aus einer Datenbank verwendet wird. In fast allen anderen Fällen handelt es sich um einen Codegeruch, normalerweise um die Anfänge eines anämischen Domänenmodells oder zumindest eines Poltergeists .
Wiederholen:
Flosse.
quelle
In Ihrem Beispiel
DO
gibt es keine funktionalen Abhängigkeiten (im Grunde, weil es nichts tut). Es hängt vom konkreten Typ abDO2
, daher möchten Sie möglicherweise eine Schnittstelle zum Abstrakten einführenDO2
, damit der Verbraucher seine eigene konkrete Implementierung der untergeordneten Klasse implementieren kann.Wirklich, welche Abhängigkeit würden Sie hier injizieren?
quelle
DOPersister
, die weiß, wie man persistiert,DO
und lassen Sie sie als reines Datenobjekt (meiner Meinung nach besser). Im letzteren FallDOPersister
würde mit der Datenbankabhängigkeit injiziert.Da dies ein Datenobjekt in der Datenzugriffsschicht ist, sollte es direkt von einem Datenbankdienst abhängen. Sie können dem Konstruktor einen DatabaseService angeben:
Die Injektion muss sich jedoch nicht im Konstruktor befinden. Alternativ können Sie die Abhängigkeit über jede CRUD-Methode bereitstellen. Ich bevorzuge diese Methode gegenüber der vorherigen, da Ihr Datenobjekt nicht wissen muss, wo es bestehen bleibt, bis Sie es tatsächlich beibehalten müssen.
Sie möchten die Konstruktion definitiv nicht in den CRUD-Methoden verstecken!
Eine alternative Option wäre, den DatabaseService über eine überschreibbare Klassenmethode zu erstellen.
Eine letzte Alternative ist die Verwendung eines ServiceLocator im Singleton-Stil. Obwohl ich diese Option nicht mag, ist sie einheitlich testbar.
quelle