7 Stimmen

Elegante Weise zu verhindern, zirkuläre Ereignisse in MVC?

Die Frage, kurz gesagt:

Wie unterscheidet man in MVC zwischen einem Klick auf ein Kontrollkästchen (oder eine Änderung in einem Auswahl- oder Listenfeld), der von einem Menschen ausgeht und bedeutet "Controller, ändere das Modell", und einem Klick auf ein Kontrollkästchen (oder eine Änderung in einem Auswahl- oder Listenfeld), der vom Controller ausgeht und bedeutet "Ich aktualisiere die Ansicht, weil sich das Modell geändert hat"?


Das Beispiel:

Ich habe eine JS-App (alle eine große HTML + JS-Seite; es gibt einen Server dahinter, und AJAX geht auf, aber es ist nicht wichtig für das Beispiel), die die Vorstellung von "Vertices" durch "Edges" verbunden hat. Die Benutzeroberfläche ermöglicht das Hinzufügen und Entfernen von Vertices auf einer Karte und das Aktivieren oder Deaktivieren von Edges zwischen Paaren von Vertices.

Es gibt zwei Möglichkeiten, eine Kante von Scheitelpunkt A zu Scheitelpunkt B zu deaktivieren:

  1. Klicken Sie auf die Kante, damit das Fenster "Kantendetails" die Schaltfläche "Diese Kante deaktivieren" anzeigt; oder
  2. Klicken Sie auf Scheitelpunkt A (oder B), um im Fenster "Scheitelpunktdetails" eine Checkliste der nahegelegenen Scheitelpunkte anzuzeigen, in der Sie die Markierung für Scheitelpunkt B (oder A) aufheben können.

So funktioniert das unter der Haube von MVC ( aber siehe das Ende dieses Beitrags für ein Update, in dem ich Probleme in meinem Verständnis korrigiere ):

  • Modell: eine Liste von Vertex-Objekten und eine Liste von Edge-Objekten.
  • Ansicht: eine GMaps-Benutzeroberfläche mit Markern und Polylinien sowie Kontrollkästchen und Schaltflächen und DIVs "Vertex Details" und "Edge Details".
  • Controller:
    • JS-Funktionen, die das Modell aktualisieren, wenn Ereignisse auf den Kontrollkästchen und Schaltflächen ausgelöst werden; und
    • JS-Funktionen, die die Ansicht aktualisieren, wenn Ereignisse auf den Modellen ausgelöst werden.

Hier ist die spezifische Unzulänglichkeit :

  1. Der Benutzer hat das Scheitelpunkt-Detailfenster auf Scheitelpunkt A und das Kanten-Detailfenster auf die Kante von Scheitelpunkt A zu Scheitelpunkt B fokussiert.
  2. Der Benutzer klickt auf "Diesen Rand deaktivieren" im Fenster Randdetails.
  3. Controller-Funktion 1 empfängt das Click-Ereignis und ruft disable() für das Edge-Modellobjekt auf.
  4. Das Edge-Modellobjekt löst das Ereignis "Ich wurde gerade deaktiviert" aus.
  5. Controller-Funktion 2 empfängt das Ereignis "Ich wurde gerade deaktiviert", und
    1. zeichnet das Edge-Detailfenster neu und sagt: "Ich bin deaktiviert!" und
    2. hebt die Markierung von Scheitelpunkt B im Fenster Scheitelpunktdetails auf.
      1. Verdammt! Dadurch wird die Controller-Funktion 1 erneut ausgelöst, die auf UI-Ereignisse wartet, die bedeuten, dass eine Kante deaktiviert wurde!

Es kommt also zu einer unnötigen Neuaktualisierung des Modells und einer Neuaktualisierung der Ansicht. In einer komplexeren Ansicht mit Ereignissen, die Ereignisse auslösen, die wiederum Ereignisse auslösen, kann dies zu Dutzenden von überflüssigen Aktualisierungen führen!


Update: eine großartige Antwort.

Ich habe MVC ein wenig missverstanden. Ich habe nicht nur eine Ansicht, wie ich oben beschrieben: Ich habe mehrere Views in mehreren Models. Insbesondere habe ich eine Checkbox-Liste Ansicht der Kanten zu einem bestimmten Knoten, und eine separate, "detaillierte Fenster-Stil" Ansicht einer Kante.

Außerdem sollte ich nicht eine Controller-Funktion haben, die alle Ansichten aktualisiert, wenn sich das Modell ändert: jede Ansicht sollte die selbst wenn sich das Modell ändert.

Wenn sich also jede Ansicht für "Statusaktualisierungs"-Ereignisse auf dem Modell registriert und jede Ansicht sich selbst beim Empfang dieser Ereignisse aktualisiert, dann ist die Antwort auf meine Frage nach zirkulären Ereignissen einfach die folgende:

Die Checkbox-Listenansicht deaktiviert die Checkbox-Ereignisse für den Moment, in dem sie die Checkboxen nach einer Modellstatusänderung aktualisiert.

Wenn nun ein Benutzer eine Kante über das Detailfenster der Kante deaktiviert, aktualisiert der Controller das Kantenmodell, die Ansicht der Kontrollkästchenliste erhält eine Benachrichtigung über die Aktualisierung, und die Ansicht der Kontrollkästchenliste ist intelligent genug, um Kontrollkästchenereignisse zu unterdrücken, während sie den Status des entsprechenden Kontrollkästchens ändert.

Dies ist viel schmackhafter als meine ursprüngliche Lösung, bei der ein Controller ALLE Ansichten aktualisiert - und daher wissen muss, welche Ansichten besondere Pflege und Fütterung benötigen, um Schleifen zu vermeiden. Stattdessen muss nur die einzelne Ansicht mit störenden UI-Elementen mit dem Problem umgehen.

Vielen Dank an diejenigen, die meine Frage beantwortet haben!

3voto

wds Punkte 30993

Nur um das MVC-Modell zu rekapitulieren. Ansichten sollten sich im Allgemeinen selbst aktualisieren. So funktioniert es: Ein Controller ändert den Zustand des Modells, das Modell sendet Aktualisierungen an seine Ansichten, die Ansichten ziehen den neuen Zustand vom Modell ein und aktualisieren sich selbst. Während Controller und Views im Allgemeinen gebündelt werden (z.B. Drilling Down auf Daten in einer grafischen Darstellung), sollten sie niemals direkt interagieren, sondern nur über das Modell. Dies gilt natürlich generell.

Die JS-Funktionen, die Ihre Ansichten aktualisieren, sind also nicht wirklich Controller, was ein wichtiger Unterschied ist. Sie sollten als Teil Ihrer Ansicht betrachtet werden. Dies mag nicht hilfreich sein, um das Problem bei der Hand, aber ich dachte, es verdiente darauf hinzuweisen.

Sie können Ihr Modell auch nicht löschen, ich nehme an, Sie meinen, Sie löschen etwas de Ihr Modell, da keine Ansichten oder Controller tatsächlich existieren (oder in einem funktionalen Zustand sein) können, wenn sie nicht durch ein Modell unterstützt werden.

Nicht ein JS-Code-Jockey und nicht mit Gmaps verwendet ich nicht wirklich sehen, wo das Problem ist. Ändert den Zustand eines Kontrollkästchens (checked Eigenschaft) Feuer der onClick() Ereignis? Es sollte wirklich nicht IMHO, aber vielleicht haben sie es so implementiert, andernfalls könnten Sie einfach Ihren Controller an die onClick() und fügen Sie einige Logik, um das Kontrollkästchen (oder, dies ist JS, in einer Funktion irgendwo), um das Kontrollkästchen Zustand ändern. Wenn das nicht möglich ist, sind Option 1 und 2 definitiv Ihre beste Wette.

Zusatz: Benutzer interagiert mit einer Ansicht

Was passiert also, wenn ein Benutzer mit einer Ansicht interagieren möchte? Häufig enthält ein Widget sowohl eine Ansicht als auch einen Controller. Ein Kontrollkästchen hat einen View (man kann sehen, ob es angekreuzt ist oder nicht) und auch einen Controller (man kann es anklicken). Wenn Sie das Kontrollkästchen anklicken, sollte im Prinzip Folgendes passieren:

  • Checkbox-Controller empfängt das Ereignis
  • Checkbox-Controller ändert den Status für den Wert, den dieses Kontrollkästchen im Modell darstellt
  • Modell aktualisiert Hörer (einschließlich des Kontrollkästchens)
  • aktualisiert das Aussehen des Kontrollkästchens, um anzuzeigen, dass sich der Wert geändert hat

Der erste Schritt, wie der Controller das Ereignis empfängt, ist etwas sprachabhängig, aber in OOP-Sprachen ist es wahrscheinlich ein Listener-Objekt, das an Benutzerschnittstellenereignisse auf diesem bestimmten Widget angehängt ist, das entweder der Controller ist oder den Controller über die Benutzerinteraktion benachrichtigt.

0voto

Juliet Punkte 78591

Das ist eine schwierige Frage. Wenn ich richtig verstehe, ergibt sich das Problem, weil Sie einen Click-Handler auf Ihr Modell ausgesetzt haben, und das Modell-Click-Ereignis wird durch den Controller gefangen. Der Controller aktualisiert die Ansicht, die wiederum das gleiche Ereignis umschaltet.

Meiner Meinung nach wäre es unangemessen, wenn sich der Controller an das Click-Ereignis des Edge anhängen würde, da dies zu viele Details über die Implementierung und Verwendung des Edge preisgibt. Der Controller kümmert sich nicht darum, wie der Edge verwendet wird, und auch nicht um andere Implementierungsdetails.

Im kanonischen MVC-Stil ist es nicht erforderlich, dass sich der Controller an jede Modell-Ereignisse überhaupt nicht, weil der Zustand des Modells im Allgemeinen nicht von der Ansicht oder anderen Controllern geändert wird. Es ist nicht notwendig, dass das Model dem Controller mitteilt, dass es geändert wurde.

Um das Problem zu beheben, sollten Sie die Schnittstelle von View so definieren, dass sie eine einzige Methode hat, z. B. ToggleEdge:

public interface GraphView
{
    event Action ToggleEdge;
}

Es ist verlockend, zwei Methoden zu erstellen, EdgeClicked und CheckboxClicked, aber das Bestehen auf zwei unabhängigen Methoden wie das verletzt das Prinzip der Kapselung. Es gibt zu viele Implementierungsdetails für Ihren Controller oder jeden anderen, der auf diese Ereignisse zugreifen möchte. Denken Sie daran, dass der Controller sich nur darum kümmert, dass sich der Zustand der View geändert hat, es ist ihm egal wie hat es sich geändert.

Wenn Sie die View-Schnittstelle in Ihre Benutzeroberfläche implementieren, sollten Sie darauf achten, dass das ToggleEdge-Ereignis von eine Standort. Sie können dies tun, indem Sie sich an das Edge.Clicked-Ereignis in Ihrer Ansicht hängen und es verwenden, um Ihr Kontrollkästchen umzuschalten; dies macht Ihr Kontrollkästchen verantwortlich für das Anheben der Toggle-Entlüftung bis zum Controller:

public class UI : UserControl, GraphView
{
    public event Action ToggleEdge;

    void OnToggleEdge(Edge edge)
    {
        if (ToggleEdge != null)
            ToggleEdge(edge);
    }

    protected void Edge_Clicked(object sender, EventArgs e)
    {
        CheckBox chkbox = FindCheckBoxThatCorrespondsToEdge((Edge)sender);
        chkbox.Checked = !chkbox.Checked;    
    }

    protected void chkEdge_CheckChanged(object sender, EventArgs e)
    {
        Edge edge = FindEdgeThatCorrespondsToCheckbox((CheckBox)sender);
        OnToggleEdge(edge);
    }
}

Man kann argumentieren, dass die View jetzt zu viel über ihre Implementierung weiß: Sie weiß, dass Kanten und Checkboxen grundsätzlich miteinander verbunden sind. Vielleicht ist dies ein weiterer Hack, aber es kann wahrscheinlich als "UI-Logik" abgetan werden müssen, um die Anzeige der Ansicht synchronisiert zu halten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X