Является ли ActionListener в контроллере для Java GUI App хорошей идеей?
Я не пытаюсь следовать шаблону MVC. В интернете, как я вижу, самый известный пример - калькулятор, например, здесь. Я начал использовать эту реализацию шаблона MVC. Но теперь у меня есть некоторые сомнения относительно слушателей действия в контроллере, поскольку они имеют тенденцию двигаться к просмотру.
Основная причина того, что с просмотром связано много изменений - шрифты, цвета, границы и т. Д. Кроме того, существуют списки действий, которые изменяют только вид! В результате реализовать такой actionlistener в контроллере намного сложнее (по сравнению с простым внутренним анонимным классом). Кроме того, требуется сделать много элементов представления доступными из контроллера.
У меня была идея сохранить некоторые списки действий в контроллерах, а некоторые в поле зрения, но это может привести к путанице в будущем. Поэтому я хотел бы услышать мысли других людей.
PS Этот вопрос не дублирует шаблон MVC со многими ActionListeners
4 ответа
MVC не является "строгим" паттерном. Существуют разные интерпретации исходного шаблона и различные производные, такие как MVP или MVVM, которые часто используются (даже когда люди говорят, что используют MVC).
Наиболее важным аспектом является разделение модели и вида. Но детали того, как они связаны, могут различаться, в зависимости от случая применения.
Наиболее частый вопрос, который возникает для шаблона MVC: "Что такое контроллер?"
Ответ:
"Бухгалтер, который получил повышение"
Из моего личного опыта редко бывает причина иметь явный класс "Контроллер". Принудительное накапливание и суммирование слушателей в одном классе "Контроллер" имеет несколько серьезных недостатков. Чтобы установить соединение между компонентами графического интерфейса и моделью, у вас есть два варианта: один вариант - разрешить доступ к компонентам представления для подключения слушателей:
gui.getSomeButton().addActionListener(myActionListener);
Я думаю, что это не пойдет, потому что это раскрывает детали реализации и препятствует изменениям. Другой вариант немного лучше, а именно - предложить методы, которые позволяют подключать слушателей:
gui.addActionListenerToSomeButton(myActionListener);
Но я думаю, что это сомнительно, потому что это все еще разоблачает тот факт, что есть кнопка. Проблема может стать более очевидной, когда у вас есть, например, JTextField
ввести номер, а затем изменить его на JSlider
: Это изменит требуемые типы прослушивателей, хотя это должно быть только вопросом представления.
В приложениях Swing я думаю, что слушатели могут рассматриваться как "маленькие контроллеры". И я думаю, что вполне возможно иметь анонимных слушателей, которые напрямую вызывают методы модели (если только вокруг этих вызовов нет дополнительной логики).
Сказав это: я бы не стал рассматривать пример, который вы указали, как "хороший" пример для MVC. Прежде всего, потому что выбранный пример не показывает ключевую точку MVC: модель не содержит состояния, и тот факт, что модель в MVC обычно является той вещью, которую необходимо соблюдать (и, таким образом, как прикрепили слушатели) не становится понятным. И, во-вторых, потому что способ установления связи между GUI и моделью сомнителен из-за упомянутых выше моментов.
Мне понравился пример на http://csis.pace.edu/~bergin/mvc/mvcgui.html. Части этого также могут быть подвергнуты сомнению (например, использование общих классов Observer / Observable), но я думаю, что это хорошо показывает основную идею MVC убедительным образом.
РЕДАКТИРОВАТЬ: нет загрузки этого примера в виде ZIP или около того. Но вы можете просто скопировать и вставить TemperatureModel
, TemperatureGUI
, FarenheitGUI
а также MVCTempConvert
в IDE. (Предполагается CelsiusGUI
присутствовать. это CelsiusGUI
, опущен на веб-сайте, но структурно равен GUI Farenheit. Для первого теста строка, в которой он создается, может быть просто закомментирована).
В этом примере предлагается добавить слушателей TemperatureGUI
учебный класс. Фактические слушатели созданы и прикреплены конкретным FarenheitGUI
учебный класс. Но это более или менее деталь реализации. Ключевым моментом здесь (который также направлен на исходный вопрос) является то, что Слушатели создаются View, в форме внутренних классов или даже анонимных классов. Эти слушатели напрямую вызывают методы модели. А именно, чтобы установить температуру в Фаренгейте (для графического интерфейса Фаренгейта) или установить температуру в градусах Цельсия (для графического интерфейса Цельсия).
Есть еще некоторые степени свободы. Это не "идеальный" или "универсальный" пример MVC. Но это ИМХО лучше, чем большинство других примеров MVC, которые я нашел до сих пор, потому что он хорошо показывает важные аспекты:
- Модель
Observable
- Вид является
Observer
- "Контроллеры" (то есть прослушиватели в данном случае) - это анонимные / внутренние классы, которые поддерживаются только представлением и вызывают методы модели.
В более сложной, общей установке нельзя использовать Observable
/Observer
классы. Вместо этого можно было бы создать выделенных слушателей и, возможно, события для модели. В этом случае это может быть что-то вроде TemperatureChangedListener
а также TemperatureChangedEvent
, Observable
/Observer
классы были использованы здесь для краткости, потому что они уже являются частью стандартного API.
Начнем с того, что обратите внимание, что могут быть более сложные случаи применения, в которых идея MVC, которая представлена в этом небольшом примере, должна быть немного расширена. В частности, когда нужно выполнить задачи, которые выходят за рамки простого вызова методов модели. Например, когда представление содержит несколько полей ввода, и эти данные должны быть предварительно обработаны или проверены иным образом, прежде чем они будут переданы в модель. Такие задачи не должны выполняться анонимным слушателем. Вместо этого такие задачи могут быть обобщены в классе, который затем может быть назван "Контроллер". Однако присоединение фактических слушателей к компонентам GUI все еще может быть сделано исключительно с помощью View. В качестве чрезмерно наводящего примера: это может произойти как
// In the view:
someButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
String s = someTextField.getText();
Date d = someFormattedTextField.getDate();
int i = someSlider.getValue();
// The controller validates the given input, and
// eventually calls some methods on the Model,
// possibly using the given input values
controller.process(s, i, d);
}
});
Я обычно придерживаюсь следующего шаблона проектирования, и он хорошо работает для меня, так как довольно эффективно разделяет вид модели и контроллер
Представление: ваше представление должно содержать все ваши j-компоненты с их получателями или установщиками, компонент, связанный с размещением кода, код, связанный с макетом. Я никогда не добавляю слушателей ни к одному из компонентов в классе представления, это только имеет дело с расположением
Модель: модель должна иметь заполнители для хранения ваших данных для вашего представления, например: если у вас есть JTable, модель может содержать массив, который будет хранить данные для вашего JTable
Контроллер = Модель + представление (здесь вы связываете модель с представлением), добавьте сюда всех своих слушателей. Привязайте свои текстовые поля, поля со списком и т. Д., Добавьте свою бизнес-логику на стороне клиента, добавьте слушателей действий для своих кнопок. Здесь вы должны получить доступ к своему представлению. и модель.
Надеюсь это поможет.
Если вы следуете шаблону GRASP контроллера, который предполагает разделение уровней интерфейса и домена, actionPerformed()
код остается на уровне интерфейса. Концепция объясняется в книге, но для ясности я привожу фигуру из ресурсов преподавателя книги:
Изменить: Как я уже говорил в комментарии выше, если вы реализуете другое представление (например, в мобильном приложении или для добавления распознавания голоса), вы хотите сохранить ту же самую системную операцию, отправленную на уровень вашего домена. Мне нравится понятие уровня представления "распознавание жеста пользователя, который преобразуется в команду контроллера".
Вся логика должна быть размещена в контроллере. Однако строгой линии не существует, внутри представления может быть очень простая логика. Поместите actionListeners в контроллер, а затем ваши кнопки в GUI. Так я обычно делаю. Иногда мне лень, и пусть мои кнопки реализуют actionListeners и позволяют им слушать самих себя, в этом случае я помещаю кнопки в пакет контроллера.