Я разрабатываю Java-приложение Swing, которое будет иметь несколько подсистем. Для всех намерений и целей, давайте предположим, что я делаю программу интернет-чата со случайным дополнительным функционалом. Эта функция будет ... планировщиком, в котором вы сможете установить время и получать напоминания в это время, а также уведомлять всех в вашем списке друзей, что вы получили напоминание.
Имеет смысл объединить эти функции в три класса: GUI, ChatManager и Scheduler. Эти классы будут делать следующее:
GUI - определение всех компонентов и событий свинга
ChatManager - создание чата, отправка и получение сообщений, управление списком друзей
Планировщик - Контроль системного времени, отправка уведомлений, сохранение файла для запоминания событий между сеансами
Чтобы программа работала, каждый из этих классов должен быть способен общаться с двумя другими. Графический интерфейс должен сообщить ChatManager, когда отправлять сообщение, и сообщить планировщику, когда начинать мониторинг. ChatManager должен отображать сообщения в графическом интерфейсе, когда они получены, и, наконец, планировщик должен как уведомить графический интерфейс, когда он закончен, так и отправить обновление статуса или что-то еще в ChatManager.
Конечно, классы, описанные здесь, все довольно просты, и, возможно, было бы неплохо просто позволить им общаться друг с другом напрямую. Однако ради этого вопроса давайте предположим, что взаимодействия гораздо более сложные.
Например, допустим, мы можем зарегистрировать определенное событие в планировщике вместо определенного времени. Отправляя сообщение, я отправил его пользователю, сохранил его в файле журнала, создал объект события, передал его планировщику и обработал любые исключения, которые могут быть сгенерированы по пути.
Когда общение становится таким сложным, становится трудно поддерживать ваш код, если общение с этими классами может происходить во многих разных местах. Например, если бы мне пришлось провести рефакторинг ChatManager, мне также потребовалось бы внести существенные изменения как в GUI, так и в планировщик (и все остальное, если я представлю что-то новое). Это усложняет поддержку кода и повышает вероятность того, что мы, лишенные сна программисты, будем вносить ошибки при внесении изменений.
Решение, которое изначально казалось наиболее целесообразным, заключается в использовании шаблона проектирования посредника. Идея состоит в том, что ни один из этих трех основных классов не знает непосредственно друг о друге, и вместо этого каждый знает о классе-посреднике. Класс-посредник, в свою очередь, определяет методы, которые обрабатывают связь между тремя классами. Так, например, GUI будет вызывать метод sendMessage () в классе-посреднике, а посредник будет обрабатывать все, что должно произойти. В конечном итоге это разъединяет три основных класса, и любые изменения в одном из них, скорее всего, приведут только к изменениям в посреднике.
Однако это приводит к двум основным проблемам, которые в конечном итоге привели меня к тому, что я пришел сюда, чтобы получить обратную связь. Они следующие:
Проблемы
Многим задачам потребуется обновить графический интерфейс, но посредник не знает о компонентах. - Предположим, пользователь запускает программу и вводит свое имя пользователя / пароль и нажимает кнопку входа для входа в систему. на сервер чата. При входе в систему вы хотите сообщить о процессе входа в систему путем отображения текста на экране входа в систему, такого как «Подключение ...», «Вход в систему ...» или «Ошибка». Если вы определяете метод входа в класс Mediator, единственный способ отобразить эти уведомления - создать открытый метод в классе GUI, который обновляет правильный JLabel. В конце концов, классу GUI потребуется очень большое количество методов для обновления его компонентов, таких как отображение сообщения от конкретного пользователя, обновление списка друзей при входе / выходе из системы и т. Д. Кроме того, вы должны ожидать, что эти обновления графического интерфейса могут произойти случайно в любое время. Это нормально?
Поток диспетчеризации событий Swing. В основном вы будете вызывать методы-посредники из компонента ActionListeners, который выполняется в EDT. Однако вы не хотите отправлять сообщения или файлы для чтения / записи в EDT, иначе ваш графический интерфейс перестанет отвечать на запросы. Таким образом, было бы хорошей идеей иметь SingleThreadExecutor, доступный в объекте-посреднике, так как каждый метод в объекте-посреднике определяет новый исполняемый объект, который он может передать потоку-исполнителю? Кроме того, обновление компонентов GUI должно происходить в EDT, но этот поток Executor будет вызывать методы для обновления компонентов GUI. Следовательно, каждый публичный метод в классе GUI должен был бы представить себя в EDT для выполнения. Это необходимо?
Мне кажется, что в классе GUI много работы по обновлению каждого компонента, который каким-либо образом взаимодействует с внешним миром, причем каждый из этих методов имеет дополнительную подслушивающую информацию о том, находится ли он в EDT, и добавив себя в EDT в противном случае. Кроме того, каждый открытый метод в классе Mediator должен был бы выполнять что-то похожее: либо добавление кода Runnable в поток Mediator, либо запуск рабочего потока.
В целом, кажется, что поддерживать приложение с шаблоном посредника почти так же сложно, как поддерживать приложение без него. Итак, в этом примере, что бы вы сделали иначе, если что-нибудь?