2009-06-09 4 views
1

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

  1. 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?

  2. 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

Antwort

1

Hier wird eine Teilantwort auf Fragen entwerfen ...

Es aussieht wie du. In Ihrem Fall würde ich den Mediator als Message Dispatcher für die GUI verwenden.

Der ChatManager und der Planer würden UpdateUIMessage generieren.

Und ich würde meine GUI schreiben, die Art und Weise

public class MyView { 

    public void handleUpdateMessage(final UpdateUIMessage msg){ 
     Runnable doRun = new Runnable(){ 
      public void run(){ 
       handleMessageOnEventDispatcherThread(msg); 
      } 
     }; 
     if(SwingUtilities.isEventDispatcherThread()){ 
      doRun.run(); 
     } else { 
      SwingUtilities.invokeLater(doRun); 
     } 
    } 
} 

So haben Sie nur eine öffentliche Methode auf dem GUI, die alle EdT Sachen behandelt.

Wenn Sie eine lockere Kopplung zwischen der GUI und den anderen Komponenten haben wollen (dh Sie möchten nicht, dass die GUI die gesamte API der anderen Komponenten kennt), kann der GuiController auch ActionMessage (auf einem bestimmten Thread) veröffentlichen ?), die vom Mediator an die anderen Komponenten versandt würde.

Ich hoffe, es hilft.

3
  1. Ihre GUI-Klassen werden mit vielen Methoden enden, um es auf dem neuesten Stand zu halten, und das ist in Ordnung. Wenn es Sie beunruhigt, gibt es immer die Möglichkeit, die GUI in Unter-GUIs aufzuteilen, die jeweils eine andere Funktionalität oder eine kleine Menge verwandter Funktionen aufweisen. Die Anzahl der Methoden wird sich natürlich nicht ändern, aber sie wird besser organisiert, kohärenter und entkoppelt sein.

  2. Anstatt jede Methode in Ihrer GUI ein Runnable zu erstellen und SwingUtilities.invokeLater zu verwenden, um das Update auf dem EDT zu setzen, rate ich Ihnen, eine andere Lösung auszuprobieren. Für meine persönlichen Projekte verwende ich die Swing Application Framework (JSR296), die einige bequeme Task Klassen zum Starten von Hintergrundjobs hat und dann die succee-Methode automatisch auf dem EDT-Thread ist. Wenn Sie dies nicht verwenden können, sollten Sie versuchen, ein eigenes ähnliches Framework für Hintergrundjobs zu erstellen.

+0

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. –

+0

@Jack - genau :) – willcodejavaforfood

1

Nun, ich werde die Welt verändern, mit der Sie arbeiten. Du hast 3 Klassen und jeder von ihnen ist nur ein Beobachter der Chat-Welt. Die MVC ist der Weg, wie Sie mit Ihrem Problem umgehen. Du musst Model für deine Welt erstellen, in diesem Fall Chat-Programm. Dieses Modell speichert Daten, Chat-Warteschlangen, Freundeslisten und achtet auf Konsistenz und benachrichtigt alle, die an Änderungen interessiert sind. Außerdem wird es mehrere Beobachter geben, die sich für den Zustand der Welt interessieren und ihren Zustand dem Benutzer und Server widerspiegeln. Die GUI bringt die Visualisierung in die Freundesliste und die Nachrichtenwarteschlange und reagiert auf deren Änderungen. Der Scheduler überprüft Änderungen an geplanten Tasks und aktualisiert das Modell mit ihren Ergebnissen. Der ChatManager wird seinen Job in mehreren Klassen wie SessionManager, MessageDispatcher, MessageAcceptor usw. besser erledigen. Sie haben 3 Klassen mit leerem Zentrum. Erstellen Sie das Zentrum und verbinden Sie sie mit diesem Zentrum und Observer Pattern. Dann wird jede Klasse nur mit einer Klasse und nur mit interessanten Ereignissen arbeiten. Eine GUI-Klasse ist eine schlechte Idee. Auf weitere Unterklassen aufteilen, die die logische Gruppe repräsentieren (Ansicht des Modells). Auf diese Weise erobern Sie Ihre Benutzeroberfläche.

+0

MVC ist ein tolles Muster. Insbesondere denke ich, es wäre eine kluge Design-Wahl, wenn mehrere GUI-Klassen behandelt werden. Die GUI beobachtet jedoch nicht nur den Chat, sondern kann auch den Scheduler und eine beliebige Anzahl anderer Klassen beobachten. Darüber hinaus beobachten diese Klassen die GUI und einander. Das sind viele verschiedene Arten von Beobachtern, die Sie erstellen müssten. Sie können auch einen Befehlsansatz verwenden und alle Ereignisse in einem generischen Objekt kapseln, aber das Parsen, das die Performance stark beeinträchtigen könnte. –

+0

Nun, ich sehe, du hast meine Antwort nicht verstanden. Die wirkliche Macht macht ein Modell, das den Zustand der Welt hält. Und dann Beobachter nur für dieses Modell und Controller, die den Modellzustand verändern. Sie reduzieren Ihre Interaktion zwischen Klassen auf Controller/Observer -> Model. Zwischen C1/O1 <-> C2/O2 müssen keine Wechselwirkungen erzeugt werden. –

1

Sie möchten vielleicht ein Projekt betrachten, das ursprünglich als MVC-Framework für die Flex-Entwicklung gestartet wurde. PureMVC wurde inzwischen in viele Programmiersprachen portiert, einschließlich Java. Obwohl es nur in einem Alpha-Status ist, als dies zu schreiben!