MVC: Verstößt der Controller gegen das Prinzip der Einzelverantwortung?

16

Das Prinzip der Einzelverantwortung besagt, dass "eine Klasse einen Grund für die Änderung haben sollte".

Im MVC-Muster besteht die Aufgabe des Controllers darin, zwischen der Ansicht und dem Modell zu vermitteln. Es bietet eine Schnittstelle für die Ansicht, um vom Benutzer auf der GUI ausgeführte Aktionen zu melden (z. B. Aufrufen der Ansicht controller.specificButtonPressed()), und kann die entsprechenden Methoden für das Modell aufrufen, um seine Daten zu manipulieren oder seine Operationen aufzurufen (z. B. model.doSomething()). .

Das bedeutet, dass:

  • Der Controller muss mit der GUI vertraut sein, um der Ansicht eine geeignete Oberfläche zum Melden von Benutzeraktionen anbieten zu können.
  • Es muss auch über die Logik im Modell Bescheid wissen, um die entsprechenden Methoden im Modell aufrufen zu können.

Dies hat zwei Gründe, sich zu ändern : eine Änderung in der Benutzeroberfläche und eine Änderung in der Geschäftslogik.

Wenn sich die Benutzeroberfläche ändert, z. B. eine neue Schaltfläche hinzugefügt wird, muss der Controller möglicherweise eine neue Methode hinzufügen, damit die Ansicht einen Benutzer über das Drücken dieser Schaltfläche informiert.

Und wenn sich die Geschäftslogik im Modell ändert, muss der Controller möglicherweise Änderungen vornehmen, um die richtigen Methoden für das Modell aufzurufen.

Daher hat der Controller zwei mögliche Gründe für eine Änderung . Bricht es SRP?

Aviv Cohn
quelle
2
Ein Controller ist eine 2-Wege-Straße. Dies ist kein typischer Top-Down- oder Bottom-Up-Ansatz. Es gibt keine Möglichkeit, eine seiner Abhängigkeiten zu abstrahieren, da der Controller die Abstraktion selbst ist. Aufgrund der Art des Musters ist es hier nicht möglich, das SRP einzuhalten. Kurz gesagt: Ja, es verstößt gegen SRP, aber das ist unvermeidlich.
Jeroen Vannevel
1
Was ist der Sinn der Frage? Wenn wir alle mit "Ja, das tut es" antworten, was dann? Was ist, wenn die Antwort "nein" ist? Was ist das eigentliche Problem, das Sie mit dieser Frage lösen wollen?
Bryan Oakley
1
"ein Grund zur Änderung" bedeutet nicht "Code, der sich ändert". Wenn Sie in Ihren Variablennamen für die Klasse sieben Tippfehler machen, hat diese Klasse jetzt sieben Verantwortlichkeiten? Nein. Wenn Sie mehr als eine Variable oder mehr als eine Funktion haben, haben Sie möglicherweise auch nur eine einzige Verantwortung.
Bob

Antworten:

14

Wenn Sie konsequent weiter über das SRP nachdenken, werden Sie feststellen, dass "Einzelverantwortung" eigentlich ein schwammiger Begriff ist. Unser menschliches Gehirn ist irgendwie in der Lage, zwischen verschiedenen Verantwortlichkeiten zu unterscheiden, und mehrere Verantwortlichkeiten können zu einer "allgemeinen" Verantwortung zusammengefasst werden. Stellen Sie sich zum Beispiel vor, in einer normalen 4-Personen-Familie ist ein Familienmitglied für die Zubereitung des Frühstücks verantwortlich. Um dies zu tun, muss man Eier kochen und Brot toasten und natürlich eine gesunde Tasse grünen Tee zubereiten (ja, grüner Tee ist am besten). Auf diese Weise können Sie "Frühstück machen" in kleinere Teile zerlegen, die zusammen zu "Frühstück machen" abstrahiert werden. Beachten Sie, dass jedes Stück auch eine Verantwortung darstellt, die z. B. an eine andere Person delegiert werden kann.

Zurück zur MVC: Wenn die Vermittlung zwischen Modell und Ansicht nicht eine, sondern zwei Aufgaben hat, welche Abstraktionsebene müsste dann darüber liegen und diese beiden kombinieren? Wenn Sie keine finden können, haben Sie sie entweder nicht richtig abstrahiert oder es gibt keine, was bedeutet, dass Sie alles richtig verstanden haben. Und ich denke, dass dies bei einem Controller der Fall ist, der eine Ansicht und ein Modell verwaltet.

valenterry
quelle
1
Nur als zusätzliche Notiz. Wenn Sie controller.makeBreakfast () und controller.wakeUpFamily () haben, bricht dieser Controller die SRP, aber nicht, weil es sich um einen Controller handelt, nur weil er mehr als eine Verantwortung hat.
Bob
Vielen Dank für Ihre Antwort. Ich bin mir nicht sicher, ob ich Ihnen folge. Stimmen Sie zu, dass der Controller mehr als eine Verantwortung hat? Ich denke, das liegt daran, dass es zwei Gründe gibt, sich zu ändern (glaube ich): eine Änderung des Modells und eine Änderung der Ansicht. Stimmst du dem zu?
Aviv Cohn
1
Ja, ich kann zustimmen, dass der Controller mehr als eine Verantwortung hat. Dies sind jedoch "niedrigere" Zuständigkeiten, und das ist kein Problem, da sie auf der höchsten Abstraktionsebene nur eine Zuständigkeit haben (die von Ihnen genannten niedrigeren kombinieren) und somit nicht gegen die SRP verstoßen.
Valentinstag
1
Es ist auf jeden Fall wichtig, die richtige Abstraktionsebene zu finden. Das Beispiel "Frühstück machen" ist gut - um eine einzelne Verantwortung zu erfüllen, müssen oft eine Reihe von Aufgaben erledigt werden. Solange der Controller nur diese Aufgaben koordiniert, folgt er SRP. Aber wenn es zu viel über das Kochen von Eiern, das Zubereiten von Toast oder das Aufbrühen von Tee weiß, würde es die SRP verletzen.
Allan
Diese Antwort macht für mich Sinn. Vielen Dank, Valenterry.
J86,
9

Wenn eine Klasse "zwei mögliche Änderungsgründe" hat, verstößt sie gegen die SRP.

Ein Controller sollte normalerweise leichtgewichtig sein und die alleinige Verantwortung haben, die Domäne / das Modell als Reaktion auf ein GUI-gesteuertes Ereignis zu manipulieren. Wir können jede dieser Manipulationen als Anwendungsfälle oder Merkmale betrachten.

Wenn eine neue Schaltfläche auf der GUI hinzugefügt wird, sollte der Controller nur dann Änderungen vornehmen müssen, wenn diese neue Schaltfläche eine neue Funktion darstellt (dh im Gegensatz zu derselben Schaltfläche, die auf Bildschirm 1 vorhanden war, aber auf Bildschirm 2 noch nicht vorhanden war, und dies ist dann der Fall hinzugefügt zu Bildschirm 2). Es müsste auch eine entsprechende neue Änderung im Modell geben, um diese neue Funktionalität / Funktion zu unterstützen. Der Controller hat nur noch die Verantwortung, die Domäne / das Modell als Reaktion auf ein GUI-gesteuertes Ereignis zu manipulieren.

Wenn sich die Geschäftslogik im Modell ändert, weil ein Fehler behoben wurde, und der Controller sich ändern muss, ist dies ein Sonderfall (oder das Modell verletzt möglicherweise das Open-Closed-Prinzip). Wenn sich die Geschäftslogik im Modell ändert, um eine neue Funktionalität / Funktion zu unterstützen, hat dies nicht unbedingt Auswirkungen auf den Controller - nur, wenn der Controller diese Funktion verfügbar machen muss (was fast immer der Fall wäre, andernfalls, warum sie hinzugefügt würde) das Domain-Modell, wenn es nicht verwendet wird). In diesem Fall muss also auch der Controller geändert werden, um das Manipulieren des Domänenmodells auf diese neue Weise als Reaktion auf ein GUI-gesteuertes Ereignis zu unterstützen.

Wenn sich der Controller ändern muss, weil beispielsweise die Persistenzschicht von einer Einfachdatei in eine Datenbank geändert wird, verstößt der Controller mit Sicherheit gegen die SRP. Wenn der Controller immer auf derselben Abstraktionsebene arbeitet, kann dies zur Erzielung von SRP beitragen.

Jordan
quelle
4

Der Controller verstößt nicht gegen SRP. Wie Sie feststellen, ist es seine Aufgabe, zwischen den Modellen und der Ansicht zu vermitteln.

Das Problem mit Ihrem Beispiel ist jedoch, dass Sie Controller-Methoden mit der Logik in der Ansicht verknüpfen, d controller.specificButtonPressed. H. Wenn Sie die Methoden so benennen, dass sie den Controller an Ihre GUI binden, ist es Ihnen nicht gelungen, die Dinge richtig zu abstrahieren. Der Controller sollte bestimmte Aktionen ausführen, dh controller.saveDataoder controller.retrieveEntry. Das Hinzufügen einer neuen Schaltfläche in der GUI bedeutet nicht unbedingt, dass der Steuerung eine neue Methode hinzugefügt wird.

Ein Knopfdruck in der Ansicht bedeutet, etwas zu tun, aber was auch immer das ist, könnte auf eine beliebige Anzahl von anderen Wegen oder gar nicht durch die Ansicht ausgelöst worden sein.

Aus dem Wikipedia-Artikel über SRP

Martin definiert eine Verantwortung als Grund für eine Änderung und kommt zu dem Schluss, dass eine Klasse oder ein Modul nur einen Grund für eine Änderung haben sollte. Betrachten Sie als Beispiel ein Modul, das einen Bericht erstellt und druckt. Ein solches Modul kann aus zwei Gründen geändert werden. Erstens kann sich der Inhalt des Berichts ändern. Zweitens kann sich das Format des Berichts ändern. Diese beiden Dinge ändern sich aus sehr unterschiedlichen Gründen. eine substanzielle und eine kosmetische. Das Prinzip der Einzelverantwortung besagt, dass diese beiden Aspekte des Problems zwei getrennte Verantwortlichkeiten sind und daher in getrennten Klassen oder Modulen sein sollten. Es wäre ein schlechtes Design, zwei Dinge zu koppeln, die sich zu unterschiedlichen Zeiten aus unterschiedlichen Gründen ändern.

Der Controller kümmert sich nicht um die Ansicht, sondern nur darum, dass er bei einem Aufruf einer seiner Methoden der Ansicht bestimmte Daten zur Verfügung stellt. Es muss nur die Funktionalität des Modells kennen, sofern bekannt ist, dass Methoden aufgerufen werden müssen, über die das Modell verfügt. Mehr als das weiß es nicht.

Zu wissen, dass für ein Objekt eine Methode zum Aufrufen verfügbar ist, ist nicht dasselbe wie zu wissen, wie es funktioniert.

Schleis
quelle
1
Der Grund, warum ich dachte, dass der Controller Methoden wie enthalten sollte, specificButtonsPressed()ist, dass ich gelesen habe, dass die Ansicht nichts über die Funktionalität ihrer Schaltflächen und anderer GUI-Elemente wissen sollte. Es wurde mir beigebracht, dass beim Drücken einer Taste die Ansicht einfach dem Controller gemeldet werden sollte und der Controller entscheiden sollte, was dies bedeutet (und dann die entsprechenden Methoden für das Modell aufrufen sollte). Das Aufrufen der Ansicht controller.saveData()bedeutet, dass die Ansicht wissen muss, was dieser Tastendruck bedeutet, abgesehen von der Tatsache, dass er gedrückt wurde.
Aviv Cohn
1
Wenn ich die Idee der vollständigen Trennung zwischen einem Tastendruck und seiner Bedeutung aufgeben würde (was dazu führt, dass der Controller Methoden wie hat specificButtonPressed()), wäre der Controller in der Tat nicht so sehr an die GUI gebunden. Sollte ich die specificButtonPressed()Methoden fallen lassen? Hat der Vorteil, den ich mit diesen Methoden sehe, für Sie einen Sinn? Oder buttonPressed()lohnt es sich nicht , Methoden in der Steuerung zu haben?
Aviv Cohn
1
Mit anderen Worten (Entschuldigung für die langen Kommentare): Ich denke, dass der Vorteil von specificButtonPressed()Methoden im Controller darin besteht, dass die Ansicht von der Bedeutung des Tastendrucks vollständig getrennt wird . Der Nachteil ist jedoch, dass der Controller in gewisser Weise an die GUI gebunden ist. Welcher Ansatz ist besser?
Aviv Cohn
@prog IMO Der Controller sollte für die Ansicht blind sein. Eine Schaltfläche verfügt über eine bestimmte Funktionalität, muss jedoch nicht über die Details der Schaltfläche informiert sein. Es muss nur bekannt sein, dass Daten an eine Controllermethode gesendet werden. In dieser Hinsicht spielt der Name keine Rolle. Es kann aufgerufen werden foo, oder genauso einfach fireZeMissiles. Es wird nur wissen, dass es an eine bestimmte Funktion berichten soll. Es weiß nicht, was die Funktion tut, nur dass sie es aufruft. Der Controller kümmert sich nicht darum, wie seine Methoden aufgerufen werden, nur dass er auf bestimmte Weise reagiert, wenn dies der Fall ist.
Schleis
1

Eine einzige Verantwortung des Controllers ist der Vertrag, der zwischen der Ansicht und dem Modell vermittelt. Die Ansicht sollte nur für die Anzeige verantwortlich sein, das Modell sollte nur für die Geschäftslogik verantwortlich sein. Es liegt in der Verantwortung der Controller, diese beiden Verantwortlichkeiten miteinander zu verbinden.

Das ist alles schön und gut, aber sich ein bisschen von der akademischen Welt abzuwenden. Ein Controller in MVC besteht im Allgemeinen aus vielen kleineren Aktionsmethoden. Diese Aktionen entsprechen im Allgemeinen dem, was man tun kann. Wenn ich Produkte verkaufe, werde ich wahrscheinlich einen ProductController haben. Dieser Controller verfügt über Aktionen wie GetReviews, ShowSpecs, AddToCart ect ...

Die Ansicht hat die SRP zum Anzeigen der Benutzeroberfläche, und ein Teil dieser Benutzeroberfläche enthält eine Schaltfläche mit der Aufschrift AddToCart.

Der Controller verfügt über die SRP, um alle am Prozess beteiligten Ansichten und Modelle zu kennen.

Die AddToCart-Aktion des Controllers verfügt über die spezifische SRP, die alle Personen kennt, die beteiligt sein müssen, wenn ein Artikel einem Warenkorb hinzugefügt wird.

Das Produktmodell verfügt über die SRP zum Modellieren der Produktlogik und das ShoppingCart-Modell über die SRP zum Modellieren, wie Artikel für eine spätere Prüfung gespeichert werden. Das Benutzermodell verfügt über eine SRP, die den Benutzer modelliert, der seinem Einkaufswagen Material hinzufügt.

Sie können und sollten Modelle wiederverwenden, um Ihr Geschäft zu erledigen, und diese Modelle müssen an einem bestimmten Punkt in Ihrem Code gekoppelt werden. Die Steuerung steuert jede Art und Weise, wie die Kopplung erfolgt.

WhiteleyJ
quelle
0

Controller haben tatsächlich nur eine Verantwortung: den Anwendungsstatus basierend auf Benutzereingaben zu ändern.

Ein Controller kann Befehle an das Modell senden, um den Status des Modells zu aktualisieren (z. B. ein Dokument zu bearbeiten). Es kann auch Befehle an die zugehörige Ansicht senden, um die Darstellung des Modells in der Ansicht zu ändern (z. B. durch Scrollen durch ein Dokument).

source: wikipedia

Wenn Sie stattdessen Rails-artige "Controller" haben (die aktive Record-Instanzen und dumme Templates unter einen Hut bringen) , dann ist SRP natürlich ein Problem.

Andererseits sind Anwendungen im Rails-Stil anfangs nicht wirklich MVC.

Mefisto
quelle