用于复杂类交互的 Java Swing 设计模式

发布于 2024-07-22 20:58:23 字数 2018 浏览 5 评论 0原文

我正在开发一个 java swing 应用程序,它将有多个子系统。 出于所有意图和目的,我们假设我正在制作一个具有随机附加功能的互联网聊天程序。 该功能将是......一个调度程序,您可以在其中设置时间并在那时收到提醒,并通知您朋友列表中的每个人您收到了提醒。

将此功能组织为三个类是有意义的:GUI、ChatManager 和 Scheduler。 这些类将执行以下操作:

GUI - 定义所有 swing 组件和事件
ChatManager - 创建聊天连接、发送和接收消息、管理好友列表
调度程序 - 监视系统时间、发送通知、存储文件以记住会话之间的事件

为了使程序正常工作,这些类中的每一个都必须能够与其他两个进行通信。 GUI需要告诉ChatManager何时发送消息并告诉Scheduler何时开始监控。 ChatManager 需要在收到消息时在 GUI 上显示消息,最后,调度程序需要在完成时通知 GUI,并向 ChatManager 发送状态更新或其他内容。

当然,这里描述的类都非常简单,让它们直接相互通信可能不是一个坏主意。 然而,为了解决这个问题,我们假设交互要复杂得多。

例如,假设我们可以向调度程序注册特定事件而不是特定时间。 发送消息时,我将其发送给用户,将其存储在日志文件中,创建一个事件对象并将其传递给调度程序,并处理沿途可能抛出的任何异常。

当通信变得如此复杂时,如果与这些类的通信可能发生在许多不同的地方,那么维护代码就会变得困难。 例如,如果我要重构 ChatManager,我可能还需要对 GUI 和调度程序(以及其他任何东西,如果我引入新的东西)进行重大更改。 这使得代码难以维护,并且使我们睡眠不足的程序员在进行更改时更容易引入错误。

最初似乎最有意义的解决方案是使用中介设计模式。 这个想法是,这三个主要类都不直接相互了解,相反,每个类都知道一个中介类。 中介类又定义了处理三个类之间通信的方法。 因此,例如,GUI 将调用中介器类中的 sendMessage() 方法,并且中介器将处理需要发生的所有事情。 最终,这将三个主要类解耦,对其中一个类的任何更改可能只会导致中介器的更改。

然而,这引入了两个主要问题,最终导致我来这里寻求反馈。 它们如下:

问题

  1. 许多任务需要更新 GUI,但中介器不知道这些组件。 - 假设用户启动程序并输入他们的用户名/密码,然后单击登录以登录聊天服务器。 登录时,您希望通过在登录屏幕上显示文本来报告登录过程,例如“正在连接...”、“正在登录...”或“错误”。 如果您在 Mediator 类中定义登录方法,则显示这些通知的唯一方法是在 GUI 类中创建一个公共方法来更新正确的 JLabel。 最终,GUI 类将需要大量方法来更新其组件,例如显示来自特定用户的消息、在用户登录/注销时更新好友列表等。 此外,您还必须预料到这些 GUI 更新可能随时随机发生。 可以吗?

  2. Swing 事件调度线程。您主要会从组件 ActionListener 调用中介方法,这些方法在 EDT 上执行。 但是,您不想在 EDT 上发送消息或读/写文件,否则您的 GUI 将变得无响应。 因此,在中介对象中提供一个 SingleThreadExecutor 是一个好主意,并且中介对象中的每个方法都定义一个可以提交给执行程序线程的新的可运行对象吗? 此外,更新 GUI 组件必须在 EDT 上进行,但执行程序线程将调用方法来更新 GUI 组件。 因此,GUI 类中的每个公共方法都必须将自身提交给 EDT 来执行。 有必要吗?

对我来说,在 GUI 类中拥有一个方法来更新每个以某种方式与外部通信的组件似乎需要做很多工作,每个方法都会额外听到检查它是否在 EDT 上,并将其自身添加到否则为 EDT。 此外,Mediator 类中的每个公共方法都必须执行类似的操作,要么向 Mediator 线程添加 Runnable 代码,要么启动工作线程。

总的来说,使用中介者模式维护应用程序的工作量似乎与不使用中介者模式的应用程序的维护工作量几乎一样多。 那么,在这个例子中,您会采取什么不同的做法(如果有的话)?

I'm developing a java swing application that will have several subsystems. For all intents and purposes, let's assume that I am making an internet chat program with a random additional piece of functionality. That functionality will be... a scheduler where you can set a time and get a reminder at that time, as well as notify everyone on your friend list that you got a reminder.

It makes sense to organize this functionality into three classes: a GUI, a ChatManager, and a Scheduler. These classes would do the following:

GUI - Define all of the swing components and events
ChatManager - Create a chat connection, send and receive messages, manage friend list
Scheduler - Monitor system time, send notifications, store a file to remember events between sessions

For the program to work, each of these classes must be capable of communicating with the other two. The GUI needs to tell the ChatManager when to send a message and tell the Scheduler when to start monitoring. The ChatManager needs to display messages on the GUI when they're received, and finally, the Scheduler needs to both notify the GUI when it's finished, and send a status update or whatever to the ChatManager.

Of course, the classes as described here are all pretty simple, and it might not be a bad idea to just let them communicate with each other directly. However, for the sake of this question, let's assume the interactions are much more complex.

For example, let's say we can register a particular event with the scheduler instead of a particular time. When sending a message, I went to send it to the user, store it in a log file, create an event object and pass it to the scheduler, and handle any exceptions that might be thrown along the way.

When communication becomes this complex, it becomes difficult to maintain your code if communication with these classes can be happening in many different places. If I were to refactor the ChatManager, for example, I might also need to make significant chaneges to both the GUI and Scheduler (and whatever else, if I introduce something new). This makes the code difficult to maintain and makes us sleep-deprived programmers more likely to introduce bugs when making changes.

The solution that initially seemed to make the most sense is to use the mediator design pattern. The idea is that none of these three main classes are directly aware of each other, and instead, each is aware of a mediator class. The mediator class, in turn, defines methods that handle communication between the three classes. So, for example, the GUI would call the sendMessage() method in the mediator class, and the mediator would handle everything that needed to happen. Ultimately, this decouples the three main classes, and any changes to one of them would likely only result in changes to the mediator.

However, this introduces two main problems, which ultimately resulted in me coming here to seek feedback. They are as follows:

Problems

  1. Many tasks will need to update the GUI, but the Mediator isn't aware of the components. - Suppose the user starts the program and enters their username/password and clicks login to login to the chat server. While logging in, you want to report the login process by displaying text on the login screen, such as "Connecting...", "Logging in...", or "Error". If you define the login method in the Mediator class, the only way to display these notifications is to create a public method in the GUI class that updates the correct JLabel. Eventually, the GUI class would need a very large amount of methods for updating its components, such as displaying a message from a particular user, updating your friend list when a user logs on/off, and so on. Also, you'd have to expect that these GUI updates could randomly happen at any time. Is that okay?

  2. The Swing Event Dispatch Thread. You'll mostly be calling mediator methods from component ActionListeners, which execute on the EDT. However, you don't want to send messages or read/write files on the EDT or your GUI will become unresponsive. Thus, would it be a good idea to have a SingleThreadExecutor available in the mediator object, with every method in the mediator object defining a new runnable that it can submit to the executor thread? Also, updating GUI components has to occur on the EDT, but that Executor thread will be calling the methods to update the GUI components. Ergo, every public method in the GUI class would have to submit itself to the EDT for execution. Is that necessary?

To me, it seems like a lot of work to have a method in the GUI class to update every component that somehow communicates with the outside, with each of those methods having the additional overheard of checking if it's on the EDT, and adding itself to the EDT otherwise. In addition, every public method in the Mediator class would have to do something similar, either adding Runnable code to the Mediator thread or launching a worker thread.

Overall, it seems like it is almost as much work to maintain the application with the Mediator pattern than to maintain the application without it. So, in this example, what would you do different, if anything?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

时光与爱终年不遇 2024-07-29 20:58:23
  1. 您的 GUI 类最终将有许多方法来保持最新,这很好。 如果您担心,始终可以选择将 GUI 分解为子 GUI,每个子 GUI 具有不同的功能或一小组相关功能。 方法的数量显然不会改变,但会更有组织性、连贯性和解耦性。

  2. 不要让 GUI 中的每个方法都创建一个 Runnable 并使用 SwingUtilities.invokeLater 将该更新放在 EDT 上,我建议您尝试另一个解决方案。 对于我的个人项目,我使用 Swing 应用程序框架 (JSR296),它有一些方便的 Task 类用于启动后台作业,然后success 方法自动在 EDT 线程上。 如果您不能使用它,您应该尝试为后台作业创建您自己的类似框架。

  1. Your GUI classes will end up with many methods to keep it up to date and that is fine. If it worries you there is always the option of breaking up the GUI into sub GUIs each with a different functionality or a small set of related functionality. The number of methods will obviously not change, but it will be more organised, coherent and decoupled.

  2. Instead of having every method in your GUI create a Runnable and use SwingUtilities.invokeLater to put that update on the EDT I'd advise you to try out another solution. For my personal projects I use The Swing Application Framework (JSR296) which has some convenient Task classes for launching background jobs and then the succeed method is automatically on the EDT thread. If you cannot use this you should try and create your own similar framework for background jobs.

空城之時有危險 2024-07-29 20:58:23

在这里,部分回答您的设计问题...

看起来您希望组件之间具有松散的耦合。
在您的例子中,我将使用中介器作为 GUI 的消息调度程序。

ChatManager 和 Scheduler 将生成 UpdateUIMessage。

我会这样编写我的 GUI

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);
        }
    }
}

所以你的 GUI 上只有一个公共方法,它可以处理所有 EdT 的东西。

如果您希望 GUI 和其他组件之间具有松散耦合(意味着:您不希望 GUI 了解其他组件的所有 API),GuiController 还可以发布 ActionMessage(在特定线程上?),将由中介者分派给其他组件。

希望能帮助到你。

Here, a partial answer to you design questions...

It looks like you want to have loose coupling between your components.
In your case, I would use the mediator as a message dispatcher to the GUI.

The ChatManager and the Scheduler would generate UpdateUIMessage.

And I would write my GUI that way

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 you have only one public method on your GUI, which handles all the EdT stuff.

If you want to have a loose coupling between the GUI and the other components (meaning : you do not want the GUI to know all the API of the other components), the GuiController could also publish ActionMessage (on a specific Thread?), which would be dispatched by the mediator to the other components.

Hope it helps.

缘字诀 2024-07-29 20:58:23

好吧,我会改变你正在工作的世界。 你有 3 个类,每个类只是聊天世界的观察者。 MVC 是如何处理的方式与你的问题。 您必须为您的世界创建模型,在本例中是聊天程序。 该模型将存储数据、聊天队列、好友列表并保持一致性并通知每个对更改感兴趣的人。 此外,还会有几个对世界状态感兴趣并向用户和服务器反映其状态的观察者。 GUI 为好友列表和消息队列带来了可视化,并对它们的更改做出反应。 调度程序正在查看计划任务中的更改并使用其结果更新模型。 ChatManager 在 SessionManager、MessageDispatcher、MessageAcceptor 等几个类中会更好地完成其工作。您有 3 个中心为空的类。 创建中心并使用该中心和观察者模式将它们连接在一起。 那么每个类就只处理一个类,并且只处理有趣的事件。 一个 GUI 类是个坏主意。 分为代表逻辑组(模型视图)的更多子类。 这就是征服你的 UI 的方法。

Well, I will change the world you are working with. You have 3 classes and each of them is just observer of the chat-world. The MVC is the way how to deal with your problem. You had to create Model for your world, in this case chat program. This model will store data, chat queue, friend list and keep eye on consistency and notify everybody interested about changes. Also, there will be several observers which are interested in state of world and are reflecting its state to user and server. The GUI is bringing visualization to friends-list and message queue and reacts on their changes. The Scheduler is looking about changes in scheduled tasks and update model with their results. The ChatManager will be better doing its job in several classes like SessionManager, MessageDispatcher, MessageAcceptor etc. You have 3 classes with empty center. Create center and connect them together using this center and Observer Pattern. Then each class will deal only with one class and only with interesting events. One GUI class is bad idea. Divide to more subclasses representing logical group (view of model). This is the way how to conquer your UI.

轻许诺言 2024-07-29 20:58:23

您可能想要查看最初作为 Flex 开发的 MVC 框架开始的项目。 PureMVC 同时已移植到许多编程语言,包括 Java。 尽管在撰写本文时它还处于 alpha 状态!

You might want to look at a project that originally started as a MVC framework for Flex development. PureMVC has been ported to many programming languages meanwhile, including Java. Though it is only in a alpha status as of writing this!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文