Шаблон проектирования Java Swing для взаимодействия сложных классов - PullRequest
1 голос
/ 09 июня 2009

Я разрабатываю Java-приложение Swing, которое будет иметь несколько подсистем. Для всех намерений и целей, давайте предположим, что я делаю программу интернет-чата со случайным дополнительным функционалом. Эта функция будет ... планировщиком, в котором вы сможете установить время и получать напоминания в это время, а также уведомлять всех в вашем списке друзей, что вы получили напоминание.

Имеет смысл объединить эти функции в три класса: GUI, ChatManager и Scheduler. Эти классы будут делать следующее:

GUI - определение всех компонентов и событий свинга
ChatManager - создание чата, отправка и получение сообщений, управление списком друзей
Планировщик - Контроль системного времени, отправка уведомлений, сохранение файла для запоминания событий между сеансами

Чтобы программа работала, каждый из этих классов должен быть способен общаться с двумя другими. Графический интерфейс должен сообщить ChatManager, когда отправлять сообщение, и сообщить планировщику, когда начинать мониторинг. ChatManager должен отображать сообщения в графическом интерфейсе, когда они получены, и, наконец, планировщик должен как уведомить графический интерфейс, когда он закончен, так и отправить обновление статуса или что-то еще в ChatManager.

Конечно, классы, описанные здесь, все довольно просты, и, возможно, было бы неплохо просто позволить им общаться друг с другом напрямую. Однако ради этого вопроса давайте предположим, что взаимодействия гораздо более сложные.

Например, допустим, мы можем зарегистрировать определенное событие в планировщике вместо определенного времени. Отправляя сообщение, я отправил его пользователю, сохранил его в файле журнала, создал объект события, передал его планировщику и обработал любые исключения, которые могут быть сгенерированы по пути.

Когда общение становится таким сложным, становится трудно поддерживать ваш код, если общение с этими классами может происходить во многих разных местах. Например, если бы мне пришлось провести рефакторинг ChatManager, мне также потребовалось бы внести существенные изменения как в GUI, так и в планировщик (и все остальное, если я представлю что-то новое). Это усложняет поддержку кода и повышает вероятность того, что мы, лишенные сна программисты, будем вносить ошибки при внесении изменений.

Решение, которое изначально казалось наиболее целесообразным, заключается в использовании шаблона проектирования посредника. Идея состоит в том, что ни один из этих трех основных классов не знает непосредственно друг о друге, и вместо этого каждый знает о классе-посреднике. Класс-посредник, в свою очередь, определяет методы, которые обрабатывают связь между тремя классами. Так, например, GUI будет вызывать метод sendMessage () в классе-посреднике, а посредник будет обрабатывать все, что должно произойти. В конечном итоге это разъединяет три основных класса, и любые изменения в одном из них, скорее всего, приведут только к изменениям в посреднике.

Однако это приводит к двум основным проблемам, которые в конечном итоге привели меня к тому, что я пришел сюда, чтобы получить обратную связь. Они следующие:

Проблемы

  1. Многим задачам потребуется обновить графический интерфейс, но посредник не знает о компонентах. - Предположим, пользователь запускает программу и вводит свое имя пользователя / пароль и нажимает кнопку входа для входа в систему. на сервер чата. При входе в систему вы хотите сообщить о процессе входа в систему путем отображения текста на экране входа в систему, такого как «Подключение ...», «Вход в систему ...» или «Ошибка». Если вы определяете метод входа в класс Mediator, единственный способ отобразить эти уведомления - создать открытый метод в классе GUI, который обновляет правильный JLabel. В конце концов, классу GUI потребуется очень большое количество методов для обновления его компонентов, таких как отображение сообщения от конкретного пользователя, обновление списка друзей при входе / выходе из системы и т. Д. Кроме того, вы должны ожидать, что эти обновления графического интерфейса могут произойти случайно в любое время. Это нормально?

  2. Поток диспетчеризации событий Swing. В основном вы будете вызывать методы-посредники из компонента ActionListeners, который выполняется в EDT. Однако вы не хотите отправлять сообщения или файлы для чтения / записи в EDT, иначе ваш графический интерфейс перестанет отвечать на запросы. Таким образом, было бы хорошей идеей иметь SingleThreadExecutor, доступный в объекте-посреднике, так как каждый метод в объекте-посреднике определяет новый исполняемый объект, который он может передать потоку-исполнителю? Кроме того, обновление компонентов GUI должно происходить в EDT, но этот поток Executor будет вызывать методы для обновления компонентов GUI. Следовательно, каждый публичный метод в классе GUI должен был бы представить себя в EDT для выполнения. Это необходимо?

Мне кажется, что в классе GUI много работы по обновлению каждого компонента, который каким-либо образом взаимодействует с внешним миром, причем каждый из этих методов имеет дополнительную подслушивающую информацию о том, находится ли он в EDT, и добавив себя в EDT в противном случае. Кроме того, каждый открытый метод в классе Mediator должен был бы выполнять что-то похожее: либо добавление кода Runnable в поток Mediator, либо запуск рабочего потока.

В целом, кажется, что поддерживать приложение с шаблоном посредника почти так же сложно, как поддерживать приложение без него. Итак, в этом примере, что бы вы сделали иначе, если что-нибудь?

Ответы [ 4 ]

3 голосов
/ 09 июня 2009
  1. Ваши классы графического интерфейса будут иметь много методов, чтобы поддерживать его в актуальном состоянии, и это нормально. Если вас это беспокоит, всегда есть возможность разбить графический интерфейс на вспомогательные графические интерфейсы, каждый из которых имеет различные функции или небольшой набор связанных функций. Количество методов, очевидно, не изменится, но оно будет более организованным, согласованным и разделенным.

  2. Вместо того чтобы каждый метод в вашем графическом интерфейсе создавал Runnable и использовал SwingUtilities.invokeLater, чтобы поместить это обновление в EDT, я бы посоветовал вам попробовать другое решение. Для своих личных проектов я использую Swing Application Framework (JSR296), который имеет несколько удобных классов Task для запуска фоновых заданий, а затем метод успешно выполняется в потоке EDT. Если вы не можете использовать это, вы должны попытаться создать собственную подобную среду для фоновых заданий.

1 голос
/ 09 июня 2009

Возможно, вы захотите взглянуть на проект, который изначально начинался как инфраструктура MVC для разработки Flex. PureMVC был портирован на многие языки программирования, включая Java. Хотя это только в альфа-статусе на момент написания этого!

1 голос
/ 09 июня 2009

Хорошо, я изменю мир, с которым вы работаете. У вас есть 3 класса, и каждый из них просто наблюдатель чата. MVC - это способ решения вашей проблемы. Вы должны были создать модель для своего мира, в данном случае это программа чата. Эта модель будет хранить данные, очередь чата, список друзей, следить за согласованностью и уведомлять всех заинтересованных лиц об изменениях. Также будет несколько наблюдателей, которые интересуются состоянием мира и отражают его состояние для пользователя и сервера. Графический интерфейс приносит визуализацию в список друзей и очередь сообщений и реагирует на их изменения. Планировщик следит за изменениями в запланированных задачах и обновляет модель с их результатами. ChatManager будет лучше выполнять свою работу в нескольких классах, таких как SessionManager, MessageDispatcher, MessageAcceptor и т. Д. У вас есть 3 класса с пустым центром. Создайте центр и соедините их вместе, используя этот центр и Шаблон наблюдателя . Тогда каждый класс будет иметь дело только с одним классом и только с интересными событиями. Один класс GUI - плохая идея. Разделите на несколько подклассов, представляющих логическую группу (вид модели). Это способ завоевания вашего пользовательского интерфейса.

1 голос
/ 09 июня 2009

Здесь, частичный ответ на ваши вопросы дизайна ...

Похоже, вы хотите иметь слабую связь между вашими компонентами. В вашем случае я бы использовал посредника в качестве диспетчера сообщений в GUI.

ChatManager и Планировщик будут генерировать 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 (в определенном потоке? ), который будет отправлен посредником другим компонентам.

Надеюсь, это поможет.

...