Wie vermeide ich eine verrückte Anzahl von Schnittstellen in der Benutzeroberfläche mit Abhängigkeitsinjektion?

9

Problem
Ich habe kürzlich viel darüber gelesen, dass Singletons schlecht sind und wie die Abhängigkeitsinjektion (die ich als "Verwenden von Schnittstellen" verstehe) besser ist. Als ich einen Teil davon mit Callbacks / Interfaces / DI implementierte und mich an das Prinzip der Schnittstellentrennung hielt, war ich ziemlich durcheinander.

Die Abhängigkeiten eines übergeordneten UI-Elements, in denen im Grunde alle seiner untergeordneten Elemente zusammengefasst sind. Je weiter oben in der Hierarchie ein UI-Element liegt, desto aufgeblähter ist sein Konstruktor.

Ganz oben auf der UI-Hierarchie befand sich eine Anwendungsklasse, die Informationen zur aktuellen Auswahl und einen Verweis auf ein 3D-Modell enthielt, das Änderungen widerspiegeln muss. Die Anwendungsklasse implementierte 8 Schnittstellen, und dies war nur ein Fünftel der kommenden Produkte (/ Schnittstellen)!

Ich arbeite derzeit mit einem Singleton, der die aktuelle Auswahl und die UI-Elemente enthält und eine Funktion hat, um sich selbst zu aktualisieren. Diese Funktion durchsucht den UI-Baum und die UI-Elemente und greift dann nach Bedarf auf den aktuellen Auswahl-Singleton zu. Der Code erscheint mir auf diese Weise sauberer.

Frage
Ist ein Singleton vielleicht für dieses Projekt geeignet?
Wenn nicht, gibt es einen grundlegenden Fehler in meinem Denken und / oder in der Implementierung von DI, der es so umständlich macht?

Zusätzliche Informationen zum Projekt
Typ: Einkaufskorb für Apartments mit Schnickschnack
Größe: 2 Mannmonate für Code und Benutzeroberfläche
Wartung: Keine laufenden Updates, aber möglicherweise "Version 2.0" später
Umgebung: Verwenden von C # in Unity, das eine Entität verwendet Komponentensystem

In fast allen Fällen löst die Benutzerinteraktion mehrere Aktionen aus. Zum Beispiel, wenn der Benutzer ein Element auswählt

  • Der UI-Teil, der dieses Element und seine Beschreibung zeigt, muss aktualisiert werden. Dazu muss es auch einige Informationen von einem 3D-Modell erhalten, um den Preis zu berechnen.
  • Weiter oben in der Benutzeroberfläche muss der Gesamtpreis aktualisiert werden
  • Eine entsprechende Funktion in einer Klasse eines 3D-Modells muss aufgerufen werden, um die Änderungen dort anzuzeigen
R. Schmitz
quelle
DI geht es nicht nur um die Verwendung von Schnittstellen, sondern auch um die Möglichkeit, Konkretionen auszutauschen, was besonders im Bereich der Unit-Tests nützlich ist ...
Robbie Dee
1
Außerdem habe ich noch kein Problem gesehen, bei dem ein Singleton die bevorzugte Lösung ist. Das kanonische Beispiel, das die Leute immer zu nennen scheinen, ist ein Protokollschreiber, aber selbst hier möchten Sie möglicherweise in eine Reihe verschiedener Protokolle schreiben (Fehler, Debug usw.).
Robbie Dee
@RobbieDee Ja, bei DI geht es um mehr, aber ich kann keine Situation erkennen, in der die Verwendung einer Schnittstelle nicht DI ist. Und wenn ein Singleton nicht die bevorzugte Lösung ist - was ich auch annehme - warum ist die bevorzugte Lösung dann so chaotisch? Das ist meine Hauptfrage, aber ich wollte sie offen halten, da manchmal die allgemeine Regel nicht gilt.
R. Schmitz
Die Schnittstellen-Konkretions-Anordnung ist auch ein Merkmal von Spott-Frameworks. Sie sind auch in OO Sprachen für ein paar Jahrzehnte ...
Robbie Dee
Ich verstehe deinen Standpunkt nicht.
R. Schmitz

Antworten:

4

Ich denke, die Frage ist ein Symptom, keine Lösung.

Ich habe kürzlich viel darüber gelesen, dass Singletons schlecht sind und wie die Abhängigkeitsinjektion (die ich als "Verwenden von Schnittstellen" verstehe) besser ist. Als ich einen Teil davon mit Callbacks / Interfaces / DI implementierte und mich an das Prinzip der Schnittstellentrennung hielt, war ich ziemlich durcheinander.

Eine Lösung, die nach einem Problem sucht; Das und das Missverständnis verfälschen wahrscheinlich Ihr Design. Haben Sie diese SO-Frage zu DI vs Singleton gelesen , verschiedene Konzepte oder nicht? Ich habe das so gelesen, als würde man einfach einen Singleton einwickeln, damit der Client sich nicht mit einem Singleton befassen muss. Es ist nur eine gute alte Verkapselung. Ich denke, das ist genau richtig.


Je weiter oben in der Hierarchie ein UI-Element war, desto aufgeblähter war sein Konstruktor.

Bauen Sie zuerst die kleineren Bits und übergeben Sie sie dann an den Konstruktor des Objekts, zu dem sie gehören, und übergeben Sie das größere Objekt an den Konstruktor des nächstgrößeren Objekts ...

Wenn Sie komplexe Konstruktionsprobleme haben, verwenden Sie das Factory- oder Builder-Muster. Unterm Strich gibt es diese komplexe Konstruktion, die in ihre eigene Klasse aufgenommen wurde, um andere Klassen ordentlich, sauber, verständlich usw. zu halten.


Die Anwendungsklasse implementierte 8 Schnittstellen, und dies war nur ein Fünftel der kommenden Produkte (/ Schnittstellen)!

Dies klingt nach fehlender Erweiterbarkeit. Das Kerndesign fehlt und alles wird von oben eingepfercht. Es sollte mehr Bottom-up-Konstruktion, Zusammensetzung und Vererbung geben.

Ich frage mich, ob Ihr Design "kopflastig" ist. Klingt so, als würden wir versuchen, eine Klasse zu allem oder allem zu machen, was es sein könnte. Und die Tatsache, dass es sich um eine UI-Klasse handelt, nicht um eine Business-Domain-Klasse, lässt mich wirklich über die Trennung von Bedenken nachdenken.

Überdenken Sie Ihr Design von Anfang an und stellen Sie sicher, dass es eine solide, grundlegende Produktabstraktion gibt, auf der aufgebaut werden kann, um komplexere oder unterschiedliche Kategorienproduktionen zu erstellen. Dann haben Sie besser benutzerdefinierte Sammlungen dieser Dinge, damit Sie irgendwo Funktionen auf "Sammlungsebene" platzieren können - wie das von Ihnen erwähnte "3. Modell".


... 3D-Modell, das Änderungen widerspiegeln muss.

Vieles davon passt möglicherweise in benutzerdefinierte Sammlungsklassen. Aufgrund der Tiefe und Komplexität kann es sich auch um eine eigenständige Klassenstruktur handeln. Diese beiden Dinge schließen sich nicht gegenseitig aus.

Lesen Sie mehr über das Besuchermuster. Es ist die Idee, ganze Funktionsfutter abstrakt mit verschiedenen Typen zu verbinden.


Design und DI

90% aller Abhängigkeitsinjektionen, die Sie jemals durchführen werden, sind Konstruktorparameterübergaben. So sagt der Quy, der das Buch geschrieben hat . Gestalten Sie Ihre Klassen gut und vermeiden Sie es, diesen Denkprozess mit einer vagen Vorstellung davon zu verschmutzen, dass Sie ein DI-Container-Ding verwenden müssen. Wenn Sie es brauchen, wird Ihr Design es Ihnen sozusagen vorschlagen.


Konzentrieren Sie sich auf die Modellierung der Apartment-Shopping-Domain.

Vermeiden Sie den Designansatz von Jessica Simpson : "Ich weiß überhaupt nicht, was das bedeutet, aber ich will es."

Folgendes ist einfach falsch:

  • Ich soll Schnittstellen verwenden
  • Ich sollte keinen Singleton benutzen
  • Ich brauche DI (was auch immer das ist)
  • Ich soll Komposition verwenden, nicht Vererbung
  • Ich soll Vererbung vermeiden
  • Ich muss Muster verwenden
Radarbob
quelle
Ich habe versucht, alle von mir verwendeten Singletons loszuwerden, und einige der Dinge, die Sie gesagt haben, sind mehr oder weniger automatisch passiert. Der Rest macht auch sehr viel Sinn und ich werde Ihren Rat befolgen und das Besuchermuster usw. nachlesen. Alles in allem eine gut geschriebene Antwort, danke!
R. Schmitz
Nur ein Punkt, und ich versuche hier nicht, ein Hilfevampir zu sein, aber es war eine Art Teil der ursprünglichen Frage: Der allgemeine Konsens (und er basiert auf triftigen Gründen) scheint zu sein, dass Sie dies tatsächlich nicht tun sollten Verwenden Sie einen Singleton. Sie schreiben "'Ich sollte keinen Singleton verwenden' ist falsch" - in welcher Situation wäre es dann angebracht, einen zu verwenden?
R. Schmitz
Ich sage nur: "Es kommt darauf an." Zu oft sehe ich Maximen wörtlich genommen.
Radarbob
3

"Klassenerbschaft" ist eine rote Fahne. Hypothetisch: eine Webseite mit 5 Widgets. Die Seite ist kein Vorfahr eines der Widgets. Es kann einen Verweis auf diese Widgets enthalten. Aber es ist kein Vorfahr. Erwägen Sie anstelle der Klassenerbschaft die Verwendung von Komposition. Jedes der 5 Widgets kann für sich erstellt werden, ohne auf die anderen Widgets oder die Startseite zu verweisen. Die obere Seite wird dann mit genügend Informationen erstellt, um eine Basisseite zu erstellen und die Widget-Objekte (Sammlung) zu gestalten, die an sie übergeben werden. Die Seite ist für das Layout usw. verantwortlich, nicht jedoch für die Konstruktion und Logik der Widgets.

Sobald Sie Komposition verwenden, ist DI Ihr bester Freund. Mit DI können Sie jedes Widget gegen die in DI definierte Version oder den Widget-Typ austauschen. Die Konstruktion der Widgets wird im DI erfasst und ist von der oberen Seite getrennt. Vielleicht kann sogar die Zusammensetzung der Sammlung in Ihrem DI definiert werden. Die oberste Seite führt das Layout basierend auf den Widgets aus, die an sie übergeben wurden. Es sind keine Änderungen am Konstruktor der obersten Seite erforderlich. Die oberste Seite benötigt nur die Logik, um das Layout der Widgets durchzuführen und Informationen basierend auf den definierten Schnittstellen an das / vom Widget zu übergeben.

Fügen Sie den Widgets, die sie benötigen, eine Sammlung von Listenern hinzu, anstatt die Listener in der Kompositionskette auf und ab zu leiten. Injizieren Sie die Sammlung in einen Verlag, damit dieser in der Sammlung veröffentlichen kann. Injizieren Sie die Sammlung in die Listener, damit sie sich selbst zur Sammlung hinzufügen können. Die Sammlung umfasst alle Objekte in der Kompositionskette.

Scorpdaddy
quelle
Entschuldigung, "Klassenhierarchie" war hier falsch formuliert; Es war eigentlich keine Klassenhierarchie, sondern eine UI-Hierarchie. Die 'Widgets' sind keine Unterklassen der Anwendungsklasse, aber die 'Seite' enthält Verweise auf sie, um UI-Updates zu veröffentlichen. Mit dieser Struktur war es sehr testbar, aber es gab auch viel Aufwand, insbesondere bei den Konstruktoren, da nur viele Zuhörer für ihre (UI-) Kinder weitergegeben wurden.
R. Schmitz
@ R.Schmitz: War der Overhead wichtig? War die Benutzeroberfläche träge und war das von Ihnen beschriebene Design schuld?
Robert Harvey
@ R.Schmitz Kannst du näher darauf eingehen, "viele Zuhörer weiterzugeben"? Vielleicht ist meine Erfahrung mit DI in C # gering, aber irgendetwas sagt mir, dass dies (zumindest im Konstruktor) bei korrektem Design nicht notwendig wäre.
Katana314
@RobertHarvey Kein Leistungsverlust, es geht nur um Lesbarkeit.
R. Schmitz
@ Katana314 Wenn beispielsweise ein Artikel hinzugefügt wird, muss das 3d-Modell aktualisiert werden und gehört keiner der Produktkategorien an. Daher wird in der Anwendungsklasse darauf verwiesen, dass Artikel hinzugefügt / entfernt / geändert werden. Am Ende wäre das für jede Produktkategorie ziemlich gleich. Andere UI-Teile reagieren ebenfalls (und hören zu), aber die Nachricht muss nach oben verschoben werden, da sich dort die 3D-Modellreferenz und die tatsächlichen Daten befinden (die dann ebenfalls aktualisiert werden).
R. Schmitz