Ist es eine schlechte Praxis, Instanzen durch mehrere Ebenen zu leiten?

60

In meinem Programmdesign komme ich oft an den Punkt, an dem ich Objektinstanzen durch mehrere Klassen führen muss. Zum Beispiel, wenn ich einen Controller habe, der eine Audiodatei lädt und sie dann an einen Player weitergibt, und der Player sie an den playerRunnable weitergibt, der sie wieder an eine andere Stelle weitergibt usw. Es sieht irgendwie schlecht aus, aber ich tue es nicht weiß, wie man es vermeidet. Oder ist es in Ordnung, dies zu tun?

EDIT: Vielleicht ist das Player-Beispiel nicht das beste, weil ich die Datei später laden könnte, aber in anderen Fällen funktioniert das nicht.

Puckl
quelle

Antworten:

54

Wie andere bereits erwähnt haben, ist dies nicht unbedingt eine schlechte Praxis, aber Sie sollten darauf achten, dass Sie die Trennung der Ebenen von Bedenken und das Übergeben schichtenspezifischer Instanzen zwischen den Ebenen nicht aufheben. Zum Beispiel:

  • Datenbankobjekte sollten niemals auf höhere Ebenen übertragen werden. Ich habe Programme gesehen, die die DataAdapter- Klasse von .NET , eine DB-Zugriffsklasse, verwendeten und an die UI-Schicht weitergaben, anstatt den DataAdapter in der DAL zu verwenden, einen DTO oder eine Datenmenge zu erstellen und diese weiterzugeben. Der DB-Zugriff ist die Domäne des DAL.
  • UI-Objekte sollten natürlich auf die UI-Ebene beschränkt sein. Wieder habe ich gesehen, dass dies verletzt wurde, sowohl mit ListBoxes, die mit Benutzerdaten gefüllt sind, die an die BL-Ebene übergeben wurden, anstelle eines Arrays / DTOs seines Inhalts, als auch (ein besonderer Favorit von mir) einer DAL-Klasse, von der hierarchische Daten abgerufen wurden Die Datenbank hat nicht eine hierarchische Datenstruktur zurückgegeben, sondern nur ein TreeView-Objekt erstellt, mit Daten gefüllt und an die Benutzeroberfläche zurückgegeben, um es dynamisch einem Formular hinzuzufügen.

Wenn die Instanzen, die Sie übergeben, jedoch die DTOs oder Entitäten selbst sind, ist dies wahrscheinlich in Ordnung.

Avner Shahar-Kashtan
quelle
1
Das mag schockierend klingen, aber in den dunklen Anfängen von .NET war dies die allgemein empfohlene Vorgehensweise und wahrscheinlich besser als die meisten anderen Stacks.
Wyatt Barnett
1
Ich stimme dir nicht zu. Es ist richtig, dass Microsoft die Praxis von Single-Layer-Apps befürwortet, bei denen der Winforms-Client auch auf die Datenbank zugegriffen hat und der DataAdapter als unsichtbares Steuerelement direkt zum Formular hinzugefügt wurde. Dies ist jedoch nur eine bestimmte Architektur, die sich vom N-Tier-Setup des OP unterscheidet . In einer mehrschichtigen Architektur, und dies galt für VB6 / DNA bereits vor .NET, blieben DB-Objekte in der DB-Schicht.
Avner Shahar-Kashtan
Zur Verdeutlichung: Sie haben gesehen, wie Personen über ihre "Datenebene" direkt auf die Benutzeroberfläche zugegriffen haben (in Ihrem Beispiel Listenfelder)? Ich glaube nicht, dass ich auf einen solchen Verstoß im Produktionscode gestoßen bin. Wow.
Simon Whitehead
7
@ SimonWhitehead Genau. Leute, die sich über die Unterscheidung zwischen einer ListBox und einem Array im Klaren waren und ListBoxes als DTO verwendeten. Es war ein Moment, in dem mir klar wurde, wie viele unsichtbare Annahmen ich mache, die für andere nicht intuitiv sind.
Avner Shahar-Kashtan
1
@SimonWhitehead - Ja, leider habe ich das in VB6- und VB.NET Framework 1.1- und 2.0-Programmen gesehen und wurde beauftragt, solche Monster zu warten. Es wird sehr hässlich, sehr schnell.
Jfrankcarr
15

Interessant, dass noch niemand über unveränderliche Objekte gesprochen hat. Ich würde argumentieren, dass es eigentlich eine gute Sache ist, ein unveränderliches Objekt durch alle Ebenen zu führen , anstatt für jede Ebene viele kurzlebige Objekte zu erstellen.

Es gibt einige großartige Diskussionen über Unveränderlichkeit von Eric Lippert in seinem Blog

Andererseits würde ich argumentieren, dass das Übergeben von veränderlichen Objekten zwischen Ebenen schlechtes Design ist. Sie erstellen im Wesentlichen eine Ebene mit dem Versprechen, dass die umgebenden Ebenen sie nicht so verändern, dass der Code beschädigt wird.

M Afifi
quelle
13

Das Weitergeben von Objektinstanzen ist normal. Es reduziert die Notwendigkeit, den Status (dh Instanzvariablen) beizubehalten, und entkoppelt den Code von seinem Ausführungskontext.

Ein Problem, mit dem Sie möglicherweise konfrontiert sind, ist das Refactoring, wenn Sie die Signaturen mehrerer Methoden entlang der Aufrufkette ändern müssen, um auf die sich ändernden Parameteranforderungen einer Methode in der Nähe des unteren Endes dieser Kette zu reagieren. Es kann jedoch durch den Einsatz moderner Softwareentwicklungstools, die beim Refactoring helfen, gemildert werden.

dasblinkenlight
quelle
6
Ich würde sagen, ein weiteres Problem, dem Sie möglicherweise gegenüberstehen, ist die Unveränderlichkeit. Ich kann mich an einen sehr verwirrenden Fehler in einem Projekt erinnern, an dem ein Entwickler ein DTO geändert hat, ohne daran zu denken, dass seine Klasse nicht die einzige war, die einen Verweis auf dieses Objekt enthielt.
Phil
8

Möglicherweise geringfügig, aber es besteht die Gefahr, dass diese Referenz irgendwo in einer der Ebenen zugewiesen wird, was möglicherweise später zu einer herabhängenden Referenz oder einem Speicherverlust führt.

techfoobar
quelle
Ihr Punkt ist richtig, aber aus der OP-Terminologie ("Übergeben von Objektinstanzen") habe ich das Gefühl, dass er entweder Werte (keine Zeiger) übergibt oder über eine Umgebung mit Speicherbereinigung (Java, C #, Python, Go,. ..).
Mohammad Dehghan
7

Wenn Sie Objekte einfach deshalb weitergeben, weil sie in einem entfernten Bereich Ihres Codes benötigt werden, kann die Verwendung der Inversion von Steuerungs- und Abhängigkeitsinjektionsentwurfsmustern zusammen mit optional einem geeigneten IoC-Container Probleme beim Transport von Objektinstanzen auf angenehme Weise lösen. Ich habe es für ein mittelgroßes Projekt verwendet und würde nie wieder in Betracht ziehen, einen großen Teil des Servercodes zu schreiben, ohne ihn zu verwenden.

Steven Schlansker
quelle
Das hört sich interessant an, ich verwende bereits Konstruktor-Injection und ich denke, meine High-Level-Komponenten steuern die Low-Level-Komponenten. Wie verwenden Sie einen IOC-Container, um das Mitführen von Instanzen zu vermeiden?
Puckl
Ich glaube, ich habe die Antwort hier gefunden: martinfowler.com/articles/injection.html
Puckl
1
Nebenbei bemerkt, wenn Sie in Java arbeiten, ist Guice wirklich nett und Sie können Ihre Bindungen auf Dinge wie Anforderungen beschränken, so dass die übergeordnete Komponente den Bereich erstellt und die Instanzen zu diesem Zeitpunkt an die richtigen Klassen bindet.
Dave
4

Das Weitergeben von Daten durch mehrere Ebenen ist keine schlechte Sache, sondern die einzige Möglichkeit, mit der ein geschichtetes System arbeiten kann, ohne die geschichtete Struktur zu verletzen. Das Anzeichen für Probleme ist, dass Sie Ihre Daten an mehrere Objekte in derselben Ebene weitergeben, um Ihr Ziel zu erreichen.

Ryathal
quelle
3

Schnelle Antwort: Es ist nichts Falsches daran , Instanzen von Objekten zu übergeben. Wie bereits erwähnt, besteht der Punkt darin, die Zuweisung dieser Referenz in allen Ebenen zu überspringen, was möglicherweise zu einer baumelnden Referenz oder zu Speicherlecks führen kann.

In unseren Projekten verwenden wir diese Methode , um DTOs (Datenübertragungsobjekte) zwischen Ebenen zu übertragen. Dies ist eine sehr hilfreiche Methode . Wir verwenden unsere dto-Objekte auch wieder, um einmal komplexer zu konstruieren, beispielsweise für zusammenfassende Informationen.

EL Yusubov
quelle
3

Ich bin in erster Linie ein Web-UI-Entwickler, aber für mich scheint es, als würde sich Ihr intuitives Unbehagen weniger auf den Durchgang der Instanz als vielmehr auf die Tatsache auswirken, dass Sie mit diesem Controller ein wenig prozedural vorgehen. Sollte Ihr Controller all diese Details ins Schwitzen bringen? Warum verweist es sogar auf mehr als einen Objektnamen, um Audio abzuspielen?

Beim OOP-Design denke ich eher darüber nach, was immer grün ist und was sich mit größerer Wahrscheinlichkeit ändert. Das Thema, das geändert werden muss, ist das, was Sie in Ihre größeren Objektboxen einfügen möchten, damit Sie konsistente Benutzeroberflächen beibehalten können, selbst wenn sich die Spieler ändern oder neue Optionen hinzugefügt werden. Oder Sie möchten Audioobjekte oder -komponenten im Großhandel austauschen.

In diesem Fall muss Ihr Controller erkennen, dass eine Audiodatei abgespielt werden muss, und dann über eine konsistente / immergrüne Methode verfügen, um sie abzuspielen. Das Audioplayer-Zeug auf der anderen Seite könnte sich leicht ändern, wenn Technologie und Plattformen geändert oder neue Auswahlmöglichkeiten hinzugefügt werden. All diese Details sollten sich unter der Oberfläche eines größeren zusammengesetzten Objekts, IMO, befinden, und Sie sollten Ihren Controller nicht neu schreiben müssen, wenn sich die Details der Audiowiedergabe ändern. Wenn Sie dann eine Objektinstanz mit Details wie dem Speicherort der Datei an das größere Objekt übergeben, wird alles, was ausgetauscht wird, im Inneren eines geeigneten Kontexts ausgeführt, in dem es weniger wahrscheinlich ist, dass jemand etwas Dummes damit macht.

Also in diesem Fall denke ich nicht, dass es diese Objektinstanz ist, die herumgeworfen wird, die dich nerven könnte. Captain Picard rennt zum Maschinenraum, um den Warpkern einzuschalten, rennt zurück zur Brücke, um die Koordinaten zu zeichnen, und drückt dann nach dem Einschalten der Schilde den "Punch-It" -Knopf, anstatt einfach "Take" zu sagen uns zu Planet X bei Warp 9. Mach es so. " und seine Crew die Details aussortieren lassen. Denn wenn er damit umgeht, kann er jedes Schiff in der Flotte steuern, ohne das Layout jedes Schiffs zu kennen und zu wissen, wie alles funktioniert. Und das ist letztendlich der größte OOP-Design-Gewinn, für den es zu drehen gilt, IMO.

Erik Reppen
quelle
2

Es ist ein weit verbreitetes Design, das am Ende auftritt, obwohl es möglicherweise zu Latenzproblemen kommen kann, wenn Ihre App für diese Art von Dingen empfindlich ist.

James
quelle
2

Dieses Problem kann mit Variablen mit dynamischem Gültigkeitsbereich (sofern diese in Ihrer Sprache verfügbar sind) oder mit thread-lokalem Speicher gelöst werden. Mit diesen Mechanismen können wir einige benutzerdefinierte Variablen einer Aktivierungskette oder einem Steuerthread zuordnen, sodass wir diese Werte nicht an Code weitergeben müssen, der nichts damit zu tun hat, nur damit sie an einen anderen Code weitergegeben werden können was braucht sie.

Kaz
quelle
2

Wie die anderen Antworten gezeigt haben, handelt es sich nicht um ein von Natur aus schlechtes Design. Dadurch kann eine enge Kopplung zwischen den verschachtelten Klassen und denen, die sie verschachteln, hergestellt werden. Das Lösen der Kopplung ist jedoch möglicherweise keine gültige Option, wenn das Verschachteln der Referenzen einen Wert für das Design darstellt.

Eine mögliche Lösung besteht darin, die verschachtelten Referenzen in der Controller-Klasse zu "verflachen".

Anstatt einen Parameter mehrmals über verschachtelte Objekte zu übergeben, können Sie in der Controller-Klasse Verweise auf alle verschachtelten Objekte verwalten.

Wie genau dies implementiert wird (oder ob es sich überhaupt um eine gültige Lösung handelt), hängt vom aktuellen Design des Systems ab, z.

  • Können Sie eine Art Karte der verschachtelten Objekte im Controller verwalten, ohne dass dies zu kompliziert wird?
  • Kann das verschachtelte Objekt den Parameter sofort erkennen, wenn Sie ihn an das entsprechende verschachtelte Objekt übergeben, oder ist beim Durchlaufen der verschachtelten Objekte eine zusätzliche Funktionalität aufgetreten?
  • usw.

Dies ist ein Problem, das ich in einem MVC-Entwurfsmuster für einen GXT-Client festgestellt habe. Unsere GUI-Komponenten enthielten verschachtelte GUI-Komponenten für mehrere Ebenen. Als die Modelldaten aktualisiert wurden, haben wir sie schließlich durch die verschiedenen Ebenen geleitet, bis sie die entsprechenden Komponenten erreicht haben. Dies führte zu einer unerwünschten Kopplung zwischen den GUI-Komponenten, da, wenn eine neue GUI-Komponentenklasse Modelldaten aufnehmen soll, Methoden zum Aktualisieren der Modelldaten in allen GUI-Komponenten erstellt werden müssen, die die neue Klasse enthalten.

Um dies zu beheben, haben wir in der View-Klasse eine Zuordnung von Verweisen auf alle verschachtelten GUI-Komponenten beibehalten, sodass die View die aktualisierten Modelldaten bei jeder Aktualisierung der Modelldaten direkt an die GUI-Komponenten senden konnte, die sie benötigt haben . Dies hat gut funktioniert, da es nur einzelne Instanzen jeder GUI-Komponente gab. Ich sah, dass es nicht so gut funktionierte, wenn es mehrere Instanzen einiger GUI-Komponenten gab, was es schwierig machte, zu identifizieren, welche Kopie aktualisiert werden musste.

David Kaczynski
quelle
0

Was Sie beschreiben, ist ein sogenanntes Chain-of-Responsibility- Entwurfsmuster. Apple verwendet dieses Muster für sein Event-Handling-System.

user8865
quelle