Ich entwickle eine Java Swing-Anwendung, die mehrere Subsysteme haben wird. Nehmen wir an, dass ich ein Internet-Chat-Programm mit einer zufälligen zusätzlichen Funktionalität mache. Diese Funktionalität wird ... ein Scheduler sein, wo du eine Zeit festlegen und eine Erinnerung zu dieser Zeit erhalten kannst, sowie jeden auf deiner Freundesliste benachrichtigen können, dass du eine Erinnerung bekommen hast.Java Swing Entwurfsmuster für komplexe Klasseninteraktion
Es ist sinnvoll, diese Funktionalität in drei Klassen zu gliedern: eine GUI, einen ChatManager und einen Scheduler. Diese Klassen würden wie folgt vor:
GUI - Definieren Sie alle der Swing-Komponenten und Ereignisse
ChatManager - eine Chat-Verbindung erstellen, Nachrichten senden und empfangen, verwalten Freundesliste
Scheduler - Monitor-System Zeit, Benachrichtigungen senden, eine Datei speichern, um Ereignisse zwischen Sitzungen zu speichern
Damit das Programm funktioniert, muss jede dieser Klassen in der Lage sein, mit den anderen beiden zu kommunizieren. Die GUI muss dem ChatManager mitteilen, wann eine Nachricht gesendet werden soll und dem Scheduler mitteilen, wann er mit der Überwachung beginnen soll. Der ChatManager muss Nachrichten auf der GUI anzeigen, wenn sie empfangen werden, und schließlich muss der Scheduler sowohl die GUI benachrichtigen, wenn sie fertig ist, als auch eine Statusaktualisierung oder Ähnliches an den ChatManager senden.
Natürlich sind die hier beschriebenen Klassen ziemlich einfach, und es ist vielleicht keine schlechte Idee, sie einfach direkt miteinander kommunizieren zu lassen. Nehmen wir jedoch für diese Frage an, dass die Interaktionen viel komplexer sind.
Nehmen wir zum Beispiel an, dass wir ein bestimmtes Ereignis mit dem Scheduler statt einer bestimmten Zeit registrieren können. Beim Senden einer Nachricht habe ich sie an den Benutzer gesendet, in einer Protokolldatei gespeichert, ein Ereignisobjekt erstellt und an den Scheduler übergeben und alle möglichen Ausnahmen behandelt.
Wenn die Kommunikation so komplex wird, wird es schwierig, Ihren Code zu pflegen, wenn die Kommunikation mit diesen Klassen an vielen verschiedenen Orten stattfinden kann. Wenn ich zum Beispiel den ChatManager umgestalten würde, müsste ich möglicherweise auch wichtige Änderungen an der GUI und am Scheduler vornehmen (und was auch immer, wenn ich etwas Neues vorstelle). Dies macht den Code schwierig zu pflegen und macht uns schlaflose Programmierer wahrscheinlicher, Fehler einzuführen, wenn sie Änderungen vornehmen.
Die Lösung, die zunächst am sinnvollsten erschien, ist die Verwendung des Mediator-Entwurfsmusters. Die Idee ist, dass keine dieser drei Hauptklassen einander direkt kennt und stattdessen jeder eine Vermittlerklasse kennt. Die Mediator-Klasse wiederum definiert Methoden, die die Kommunikation zwischen den drei Klassen handhaben. So würde beispielsweise die GUI die sendMessage() -Methode in der Mediator-Klasse aufrufen, und der Mediator würde alles handhaben, was passieren musste. Letztendlich entkoppelt dies die drei Hauptklassen, und jede Änderung an einer von ihnen würde wahrscheinlich nur zu Änderungen des Mediators führen.
Dies führt jedoch zu zwei Hauptproblemen, die letztendlich dazu führten, dass ich hierher kam, um Rückmeldungen zu erhalten. Sie sind wie folgt:
Probleme
Viele Aufgaben müssen die GUI aktualisieren, aber der Mediator ist nicht bekannt, die Komponenten. - Angenommen, der Benutzer startet das Programm und gibt seinen Benutzernamen/sein Passwort ein und klickt auf Anmelden, um sich beim Chat-Server anzumelden. Beim Anmelden möchten Sie den Anmeldevorgang melden, indem Sie auf dem Anmeldebildschirm Text anzeigen, z. B. "Verbindung herstellen ...", "Anmelden ..." oder "Fehler". Wenn Sie die Anmeldemethode in der Mediator-Klasse definieren, besteht die einzige Möglichkeit zum Anzeigen dieser Benachrichtigungen darin, eine öffentliche Methode in der GUI-Klasse zu erstellen, die das korrekte JLabel aktualisiert. Schließlich benötigt die GUI-Klasse sehr viele Methoden zum Aktualisieren ihrer Komponenten, z. B. Anzeigen einer Nachricht von einem bestimmten Benutzer, Aktualisieren der Freundesliste bei der An-/Abmeldung eines Benutzers und so weiter. Außerdem müssten Sie erwarten, dass diese GUI-Updates jederzeit zufällig auftreten können. Ist das in Ordnung?
Der Swing Event Dispatch Thread. Sie rufen meistens Mediator-Methoden von der Komponente ActionListeners auf, die auf dem EDT ausgeführt werden. Sie möchten jedoch keine Nachrichten senden oder Dateien auf dem EDT lesen/schreiben oder Ihre GUI reagiert nicht mehr. Wäre es also eine gute Idee, einen SingleThreadExecutor im Mediator-Objekt verfügbar zu haben, wobei jede Methode im Mediator-Objekt ein neues Runnable definiert, das sie an den Executor-Thread senden kann? Außerdem muss die Aktualisierung der GUI-Komponenten im EDT erfolgen, aber der Executor-Thread wird die Methoden zum Aktualisieren der GUI-Komponenten aufrufen. Ergo müsste jede öffentliche Methode in der GUI-Klasse zur Ausführung an den EDT übergeben werden. Ist das notwendig?
Für mich scheint es wie eine Menge Arbeit eine Methode in der GUI-Klasse zu haben, jede Komponente zu aktualisieren, die in irgendeiner Weise mit der Außenseite in Verbindung steht, mit jedem dieser Methoden, um die die Überprüfung mitgehört zusätzlichen haben, wenn sie eingeschaltet ist der EDT, und fügt sich sonst dem EDT hinzu. Darüber hinaus müsste jede öffentliche Methode in der Mediator-Klasse etwas Ähnliches tun, entweder durch Hinzufügen von ausführbarem Code zum Mediator-Thread oder durch Starten eines Worker-Threads.
Insgesamt scheint es fast so viel Arbeit zu sein, die Anwendung mit dem Mediator-Muster zu pflegen, als die Anwendung ohne sie zu erhalten. Was würdest du in diesem Beispiel anders machen, wenn überhaupt? haben wollen lose Kopplung zwischen Komponenten
Die Aufteilung der GUI in mehrere Klassen und die Kommunikation des Mediators mit einem GUI-Controller klingt nach einer guten Idee. Es reduziert nicht die Anzahl der Methoden, aber es macht es in vielerlei Hinsicht viel besser organisiert. Die SwingWorker API (glaube ich in JDK6 enthalten) bietet eine praktische Schnittstelle zum Starten von Hintergrund-Threads und zum Ausführen von Methoden auf dem EDT entweder während oder nach der Lebensdauer des Threads. Wenn der Mediator diese verwendet, kann er die GUI einfach benachrichtigen, wenn bestimmte lang andauernde Aufgaben beendet sind. –
@Jack - genau :) – willcodejavaforfood