Ist es problematisch, in einer geschichteten Softwarearchitektur eine Abhängigkeit zwischen Objekten derselben Ebene zu haben?

12

In Anbetracht einer mittelgroßen Software mit einer n-Ebenen-Architektur und Abhängigkeitsinjektion ist es angenehm zu sagen, dass ein Objekt, das zu einer Ebene gehört, von Objekten aus niedrigeren Ebenen abhängen kann, jedoch niemals von Objekten aus höheren Ebenen.

Ich bin mir jedoch nicht sicher, was ich von Objekten halten soll, die von anderen Objekten derselben Ebene abhängen.

Nehmen wir als Beispiel eine Anwendung mit drei Ebenen und mehreren Objekten wie dem im Bild an. Offensichtlich sind Abhängigkeiten von oben nach unten (grüne Pfeile) in Ordnung, Abhängigkeiten von unten nach oben (roter Pfeil) sind nicht in Ordnung, aber was ist mit einer Abhängigkeit innerhalb derselben Ebene (gelber Pfeil)?

Bildbeschreibung hier eingeben

Abgesehen von zirkulären Abhängigkeiten bin ich neugierig auf andere Probleme, die auftreten könnten, und darauf, inwieweit in diesem Fall die Ebenenarchitektur verletzt wird.

bracco23
quelle
Wenn Sie keine Zyklen haben, in einer Schicht , wo Sie die horysontal Links dort haben existiert in Unterschichten Spaltung , die nur Abhängigkeiten zwischen den Schichten hat
max630

Antworten:

10

Ja, Objekte in einer Ebene können direkte Abhängigkeiten untereinander aufweisen, manchmal sogar zyklische - das ist der eigentliche Unterschied zu den zulässigen Abhängigkeiten zwischen Objekten in verschiedenen Ebenen, in denen entweder keine direkten Abhängigkeiten zulässig sind, oder nur eine strenge Abhängigkeit Richtung.

Dies bedeutet jedoch nicht, dass sie solche Abhängigkeiten in willkürlicher Weise haben sollten. Es hängt tatsächlich davon ab, was Ihre Ebenen darstellen, wie groß das System ist und welche Verantwortung die Teile haben sollten. Beachten Sie, dass "Layered Architecture" ein vager Begriff ist. Es gibt eine große Variation dessen, was dies in verschiedenen Arten von Systemen bedeutet.

Angenommen, Sie haben ein "horizontal geschichtetes System" mit einer Datenbankschicht, einer Geschäftsschicht und einer Benutzerschnittstellenschicht. Nehmen wir an, die UI-Ebene enthält mindestens mehrere Dutzend verschiedene Dialogklassen.

Man kann ein Design wählen, bei dem keine der Dialogklassen direkt von einer anderen Dialogklasse abhängt. Man kann ein Design wählen, bei dem "Hauptdialoge" und "Unterdialoge" existieren und es nur direkte Abhängigkeiten von "Haupt" zu "Unterdialogen" gibt. Oder Sie bevorzugen ein Design, bei dem eine vorhandene UI-Klasse eine andere UI-Klasse aus derselben Ebene verwenden / wiederverwenden kann.

Dies sind alles mögliche Entwurfswahlen, die je nach Art des von Ihnen erstellten Systems mehr oder weniger sinnvoll sein können, aber keine von ihnen macht die "Schichtung" Ihres Systems ungültig.

Doc Brown
quelle
Was sind die Vor- und Nachteile der inneren Abhängigkeiten, wenn man das Beispiel der Benutzeroberfläche fortsetzt? Ich kann sehen, dass es die Wiederverwendung erleichtert und zyklische Abhängigkeiten einführt, was je nach verwendeter DI-Methode ein Problem sein kann. Noch etwas?
Bracco23
@ bracco23: Das Für und Wider von "inneren" Abhängigkeiten ist dasselbe wie das Für und Wider von willkürlichen Abhängigkeiten. "Nachteile" sind, dass sie die Wiederverwendung und das Testen von Komponenten erschweren, insbesondere wenn diese von anderen Komponenten isoliert sind. Profis sind, explizite Abhängigkeiten machen Dinge, die Zusammenhalt erfordern, einfacher zu verwenden, zu verstehen, zu verwalten und zu testen.
Doc Brown
14

Es ist angenehm zu sagen, dass ein Objekt, das zu einer Ebene gehört, von Objekten aus niedrigeren Ebenen abhängen kann

Um ehrlich zu sein, ich denke nicht, dass Sie sich damit wohlfühlen sollten. Wenn es um alles andere als ein triviales System geht, möchte ich sicherstellen, dass alle Ebenen immer nur von Abstraktionen von anderen Ebenen abhängen. sowohl niedriger als auch höher.

So sollte zum Beispiel Obj 1nicht davon abhängen Obj 3. Es sollte eine Abhängigkeit von zB haben IObj 3und sollte angegeben werden, mit welcher Implementierung dieser Abstraktion zur Laufzeit gearbeitet werden soll. Das, was erzählt wird, sollte in keinem Zusammenhang mit einer der Ebenen stehen, da es die Aufgabe ist, diese Abhängigkeiten abzubilden. Das könnte ein IoC-Container sein, der zB mainmit reinem DI aufgerufen wird . Oder auf Knopfdruck könnte es sogar ein Service-Locator sein. Unabhängig davon bestehen die Abhängigkeiten zwischen den Layern erst dann, wenn das Ding das Mapping bereitstellt.

Ich bin mir jedoch nicht sicher, was ich von Objekten halten soll, die von anderen Objekten derselben Ebene abhängen.

Ich würde argumentieren, dass dies das einzige Mal ist, dass Sie direkte Abhängigkeiten haben sollten . Es ist Teil der inneren Funktionsweise dieser Ebene und kann geändert werden, ohne andere Ebenen zu beeinflussen. Es ist also keine schädliche Kupplung.

David Arno
quelle
Danke für die Antwort. Ja, die Ebene sollte keine Objekte, sondern Schnittstellen anzeigen. Das weiß ich, aber ich habe es der Einfachheit halber weggelassen.
Bracco23
4

Schauen wir uns das praktisch an

Bildbeschreibung hier eingeben

Obj 3Jetzt weiß, Obj 4existiert. Na und? Warum kümmern wir uns?

DIP sagt

"High-Level-Module sollten nicht von Low-Level-Modulen abhängen. Beide sollten von Abstraktionen abhängen."

OK, aber sind nicht alle Objekte Abstraktionen?

DIP sagt auch

"Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen."

OK, aber wenn mein Objekt richtig gekapselt ist, verbirgt das keine Details?

Manche Leute bestehen blind darauf, dass jedes Objekt eine Schlüsselwortschnittstelle benötigt. Ich bin keiner von ihnen. Ich möchte blind darauf bestehen, dass, wenn Sie sie jetzt nicht verwenden, Sie einen Plan brauchen, um damit umzugehen, dass Sie später so etwas brauchen.

Wenn Ihr Code in jeder Version vollständig überarbeitet werden kann, können Sie Schnittstellen später bei Bedarf extrahieren. Wenn Sie Code veröffentlicht haben, den Sie nicht neu kompilieren möchten, und sich wünschen, Sie hätten über eine Benutzeroberfläche gesprochen, benötigen Sie einen Plan.

Obj 3weiß Obj 4existiert. Aber weiß Obj 3, ob Obj 4es konkret ist?

Genau deshalb ist es so schön, sich NICHT newüberall zu verbreiten . Wenn Sie Obj 3nicht wissen, ob Obj 4es konkret ist, wahrscheinlich, weil es es nicht erstellt hat, dann ist es Ihnen egal, wenn Sie sich später einschleichen und sich Obj 4in eine abstrakte Klasse verwandeln Obj 3.

Wenn du das schaffst, Obj 4war es die ganze Zeit über völlig abstrakt. Das einzige, was Sie tun können, wenn Sie von Anfang an eine Schnittstelle zwischen ihnen herstellen, ist die Gewissheit, dass niemand versehentlich Code hinzufügt, der verrät, dass dies Obj 4gerade konkret ist. Geschützte Konstruktoren können dieses Risiko mindern, aber das führt zu einer anderen Frage:

Befinden sich Obj 3 und Obj 4 im selben Paket?

Objekte werden häufig auf irgendeine Weise gruppiert (Paket, Namespace usw.). Wenn sie sinnvoll gruppiert werden, ändern sich eher die Auswirkungen innerhalb einer Gruppe als zwischen Gruppen.

Ich mag es, nach Merkmalen zu gruppieren. Wenn Obj 3und Obj 4in der gleichen Gruppe und Schicht ist , ist es sehr unwahrscheinlich , dass Sie eine veröffentlicht werden und nicht wollen , dass es Refactoring während benötigen nur die andere zu ändern. Das heißt, es ist weniger wahrscheinlich, dass diese Objekte von einer Abstraktion profitieren, bevor ein eindeutiger Bedarf besteht.

Wenn Sie jedoch eine Gruppengrenze überschreiten, ist es eine gute Idee, Objekte auf beiden Seiten unabhängig voneinander variieren zu lassen.

Es sollte so einfach sein, aber leider haben sowohl Java als auch C # unglückliche Entscheidungen getroffen, die dies erschweren.

In C # ist es Tradition, jede Schlüsselwortschnittstelle mit einem IPräfix zu benennen . Das zwingt die Kunden zu WISSEN, dass sie mit einer Schlüsselwortschnittstelle sprechen. Das steht im Widerspruch zum Refactoring-Plan.

In Java ist es Tradition, ein besseres Benennungsmuster zu verwenden: FooImple implements FooDies hilft jedoch nur auf Quellcodeebene, da Java Schlüsselwortschnittstellen in eine andere Binärdatei kompiliert. Das bedeutet, dass beim Refactoring Foovon konkreten zu abstrakten Clients, bei denen kein einziges Zeichen des Codes geändert werden muss, noch eine Neukompilierung erforderlich ist.

Es sind diese BUGS in diesen bestimmten Sprachen, die die Menschen davon abhalten, formelle Abstracts zu erstellen, bis sie sie wirklich brauchen. Sie haben nicht gesagt, welche Sprache Sie verwenden, aber Sie verstehen, dass es einige Sprachen gibt, die diese Probleme einfach nicht haben.

Sie haben nicht angegeben, welche Sprache Sie verwenden. Ich fordere Sie daher dringend auf, Ihre Sprache und Situation sorgfältig zu analysieren, bevor Sie entscheiden, dass es sich überall um Keyword-Schnittstellen handelt.

Dabei spielt das YAGNI-Prinzip eine zentrale Rolle. Aber auch "Bitte mach es mir schwer, in den Fuß zu schießen".

kandierte_orange
quelle
0

Zusätzlich zu den obigen Antworten, denke ich, könnte es Ihnen helfen, es aus verschiedenen Blickwinkeln zu betrachten.

Zum Beispiel vom Standpunkt der Abhängigkeitsregel . DR ist eine Regel, die Robert C. Martin für seine berühmte saubere Architektur vorgeschlagen hat .

Es sagt

Quellcodeabhängigkeiten dürfen nur nach innen auf übergeordnete Richtlinien verweisen.

Mit übergeordneten Richtlinien meint er eine höhere Abstraktionsebene. Komponenten, bei denen Implementierungsdetails verloren gehen, wie beispielsweise Schnittstellen oder abstrakte Klassen im Gegensatz zu konkreten Klassen oder Datenstrukturen.

Die Regel ist nämlich nicht auf die Inter-Layer-Abhängigkeit beschränkt. Es wird nur auf die Abhängigkeit zwischen Codeteilen hingewiesen, unabhängig von der Position oder der Ebene, zu der sie gehören.

Nein, es ist nicht von Natur aus falsch, Abhängigkeiten zwischen Elementen derselben Ebene zu haben. Die Abhängigkeit kann jedoch immer noch implementiert werden, um mit dem stabilen Abhängigkeitsprinzip zu vermitteln .

Ein weiterer Standpunkt ist SRP.

Die Entkopplung ist unser Weg, um schädliche Abhängigkeiten zu lösen und einige bewährte Methoden wie die Abhängigkeitsinversion (IoC) zu vermitteln. Die Elemente, die dieselben Änderungsgründe haben, geben jedoch keine Gründe für die Entkopplung an, da sich Elemente mit demselben Änderungsgrund (sehr wahrscheinlich) zur selben Zeit ändern und auch zur selben Zeit bereitgestellt werden. Wenn das zwischen Obj3und Obj4dann noch einmal der Fall ist , ist nichts von Natur aus falsch.

Laiv
quelle