it-swarm.com.de

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

In Anbetracht einer mittelgroßen Software mit einer n-Schicht-Architektur und Abhängigkeitsinjektion kann ich mit Sicherheit sagen, dass ein zu einer Schicht gehörendes Objekt von Objekten aus niedrigeren Schichten abhängen kann, jedoch niemals von Objekten aus höheren Schichten.

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 Top-Down-Abhängigkeiten (grüne Pfeile) in Ordnung, Bottom-Up-Abhängigkeiten (roter Pfeil) sind nicht in Ordnung, aber was ist mit einer Abhängigkeit innerhalb derselben Ebene (gelber Pfeil)?

(enter image description here

Mit Ausnahme der zirkulären Abhängigkeit bin ich gespannt auf jedes andere Problem, das auftreten könnte, und darauf, wie sehr die geschichtete Architektur in diesem Fall verletzt wird.

12
bracco23

Ja, Objekte in einer Ebene können haben direkte Abhängigkeiten untereinander, manchmal sogar zyklische - genau das macht den Kernunterschied zu den zulässigen Abhängigkeiten zwischen Objekten in verschiedenen Ebenen aus, bei denen entweder keine direkten Abhängigkeiten bestehen erlaubt, oder nur eine strenge Abhängigkeitsrichtung.

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

Angenommen, Sie haben ein "horizontal geschichtetes System" mit einer Datenbankschicht, einer Geschäftsschicht und einer Benutzeroberflächenschicht. 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 "Hauptdialogen" zu "Unterdialogen" gibt. Oder man bevorzugt ein Design, bei dem jede vorhandene UI-Klasse jede andere UI-Klasse aus derselben Ebene verwenden/wiederverwenden kann.

Dies sind alles mögliche Entwurfsoptionen, die je nach Art des zu erstellenden Systems möglicherweise mehr oder weniger sinnvoll sind, aber keine davon macht die "Schichtung" Ihres Systems ungültig.

10
Doc Brown

Ich kann mit Sicherheit sagen, dass ein Objekt, das zu einer Ebene gehört, von Objekten aus niedrigeren Ebenen abhängen kann

Um ehrlich zu sein, denke ich nicht, dass Sie sich damit wohlfühlen sollten. Wenn ich mich mit etwas anderem als einem trivialen System befasse, möchte ich sicherstellen, dass alle Ebenen immer nur von Abstraktionen von anderen Ebenen abhängen. sowohl niedriger als auch höher.

So sollte beispielsweise Obj 1 Nicht von Obj 3 Abhängen. Es sollte eine Abhängigkeit von zB IObj 3 Haben und sollte wissen, mit welcher Implementierung dieser Abstraktion es zur Laufzeit arbeiten soll. Die Sache, die das Erzählen macht, sollte nicht mit einer der Ebenen zusammenhängen, da es Aufgabe ist, diese Abhängigkeiten abzubilden. Dies kann ein IoC-Container sein, ein benutzerdefinierter Code, der z. B. von main aufgerufen wird und reines DI verwendet. Oder bei einem Push könnte es sogar ein Service Locator sein. Unabhängig davon bestehen die Abhängigkeiten zwischen den Ebenen erst, wenn das Objekt die Zuordnung 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 sollten direkte Abhängigkeiten haben. Es ist Teil des Innenlebens dieser Ebene und kann geändert werden, ohne andere Ebenen zu beeinflussen. Es ist also keine schädliche Kopplung.

14
David Arno

Schauen wir uns das praktisch an

(enter image description here

Obj 3 weiß jetzt Obj 4 existiert. Na und? Warum interessiert es 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?

Einige Leute möchten blind darauf bestehen, dass jedes Objekt eine Schlüsselwortschnittstelle benötigt. Ich bin keiner von ihnen. Ich möchte blind darauf bestehen, dass man, wenn man sie jetzt nicht benutzt, einen Plan braucht, um später so etwas zu brauchen.

Wenn Ihr Code in jeder Version vollständig refaktorierbar ist, können Sie Schnittstellen später einfach extrahieren, wenn Sie sie benötigen. Wenn Sie Code veröffentlicht haben, den Sie nicht neu kompilieren möchten, und sich wünschen, über eine Schnittstelle zu sprechen, benötigen Sie einen Plan.

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

Dies hier ist der Grund, warum es so schön ist, new NICHT überall zu verbreiten. Wenn Obj 3 weiß nicht ob Obj 4 ist konkret, wahrscheinlich weil es es nicht erstellt hat. Wenn Sie sich später eingeschlichen haben und Obj 4 in eine abstrakte Klasse Obj 3 wäre mir egal.

Wenn du das kannst, dann Obj 4 war die ganze Zeit völlig abstrakt. Das einzige, was Sie von Anfang an durch eine Schnittstelle zwischen ihnen erhalten, ist die Gewissheit, dass jemand nicht versehentlich Code hinzufügt, der das Obj 4 ist gerade konkret. Geschützte Konstruktoren können dieses Risiko mindern, aber das führt zu einer anderen Frage:

Sind Obj 3 und Obj 4 im selben Paket?

Objekte werden häufig auf irgendeine Weise gruppiert (Paket, Namespace usw.). Bei einer klugen Gruppierung ändern sich eher die Auswirkungen innerhalb einer Gruppe als zwischen den Gruppen.

Ich mag es, nach Merkmalen zu gruppieren. Wenn Obj 3 und Obj 4 befinden sich in derselben Gruppe und Ebene. Es ist sehr unwahrscheinlich, dass Sie eine veröffentlicht haben und diese nicht umgestalten möchten, während Sie nur die andere ändern müssen. Das bedeutet, dass diese Objekte weniger davon profitieren, wenn eine Abstraktion zwischen ihnen platziert wird, bevor ein klarer Bedarf besteht.

Wenn Sie eine Gruppengrenze überschreiten, ist es wirklich 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 Keyword-Schnittstelle mit einem Präfix I zu benennen. Dies zwingt Kunden zu WISSEN, dass sie mit einer Keyword-Oberfläche sprechen. Das bringt den Refactoring-Plan durcheinander.

In Java ist es Tradition, ein besseres Namensmuster zu verwenden: FooImple implements Foo Dies hilft jedoch nur auf der Ebene des Quellcodes, da Java Schlüsselwortschnittstellen zu einer anderen Binärdatei kompiliert. Das heißt, wenn Sie Foo von konkreten zu abstrakten Clients umgestalten, die nicht ' Es muss kein einzelnes Zeichen des geänderten Codes geändert werden.

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

Sie haben nicht gesagt, welche Sprache Sie verwenden, daher möchte ich Sie dringend bitten, Ihre Sprache und Situation sorgfältig zu analysieren, bevor Sie entscheiden, dass es sich überall um Keyword-Schnittstellen handelt.

Das YAGNI-Prinzip spielt hier eine Schlüsselrolle. Aber "Bitte machen Sie es mir schwer, mich in den Fuß zu schießen".

4
candied_orange

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

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

Es sagt

Quellcode-Abhängigkeiten dürfen nur nach innen auf übergeordnete Richtlinien verweisen.

Mit übergeordneten Richtlinien meint er übergeordnete Abstraktionen. Komponenten, bei denen Implementierungsdetails verloren gehen, z. B. Schnittstellen oder abstrakte Klassen im Gegensatz zu konkreten Klassen oder Datenstrukturen.

Die Regel ist nicht auf die Abhängigkeit zwischen den Schichten 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 an sich nichts Falsches daran, Abhängigkeiten zwischen Elementen derselben Ebene zu haben. Die Abhängigkeit kann jedoch weiterhin implementiert werden, um mit dem stabiles Abhängigkeitsprinzip zu vermitteln.

Ein weiterer Standpunkt ist SRP.

Die Entkopplung ist unser Weg, um schädliche Abhängigkeiten zu überwinden und mit einigen Best Practices wie der Abhängigkeitsinversion (IoC) zu vermitteln. Die Elemente, die die Gründe für die Änderung gemeinsam haben, geben jedoch keine Gründe für die Entkopplung an, da sich Elemente mit demselben Änderungsgrund zur gleichen Zeit (sehr wahrscheinlich) ändern und auch zur gleichen Zeit bereitgestellt werden. Wenn das zwischen Obj3 und Obj4 dann ist noch einmal nichts von Natur aus falsch.

0
Laiv