Несмотря на свою простоту, паттерн MVC много раз непонятен.Сначала вам нужно понять, что следует хранить во всех этих разделенных компонентах и как они могут взаимодействовать друг с другом.
Элемент View: Any UI.Хороший многократно используемый элемент представления должен использоваться где угодно.Поэтому представление не знает ни своего контекста, ни конкретного контроллера, с которым он будет взаимодействовать.Что вид знает, так это его собственное состояние и один (или многие) универсальный слушатель, который он вызовет, когда произойдет какое-то действие.Элемент представления знает свое состояние, передает трансляцию, но не должен сам менять свое состояние.Пример: кнопка будет транслировать действие «нажал», и вы зададите имя кнопки через контроллер с помощью метода, подобного aButton.setLabel("click me!");
Модель: модель может обрабатыватьсостояние набора данных, он обычно реализует некоторые функции для сохранения и загрузки своего состояния в / из файла.Модель - это блок, который на самом деле не должен изменять свое состояние, если кто-то (контроллер) не просит об этом.Опять же, хорошая модель не знает о представлении или даже контроллере.Так оно и есть.Очень простая модель - это, например, строка.Вы можете загрузить строку из файла, сохранить ее в файл, изменить ее в верхний регистр, подстроку и так далее.У строки действительно есть состояние, она не знает своего контекста и просто ждет, пока кто-нибудь ее использует.Если модель знает контроллер, который вы обманули, модель не будет использоваться повторно, если вы не реализуете тот же контроллер (что является плохой практикой даже при использовании интерфейсов).
Контроллер: контроллерна самом деле ваша программа, это часть, где принимается решение часть.Где это ?хорошо ... ваш main function
уже является контроллером.Опять же, все решения превращаются в контроллер.Когда кто-то нажимает на кнопку, вызывается метод в контроллере (а не в представлении), когда вы хотите обновить метку, вы делаете это из контроллера.Также все потоки должны быть обработаны в контроллерах.Ваша модель загружает большой файл?Создайте поток в контроллере, попросите модель загрузить изображение, затем сообщите основному потоку, когда он закончил (не по теме, поиск цикла событий)
Несколько окон, вот мы!
Просмотр / Контроллер, как они взаимодействуют друг с другом?Ответственность за представление состоит только в том, чтобы предоставлять контроллеру события (текстовое поле изменено, кнопка была нажата и т. Д.) И иметь возможность отображаться так, как мы хотим.Например, window.setTitle ("myWindow");У нас может возникнуть соблазн поместить вещи в представление, которое должно быть в контроллере и наоборот.
Например, если вы создаете панель предупреждений, у вас будет 2 кнопки (ОК, отмена) и одна метка длятекст.Знаете ли вы, что делать, когда вы нажимаете «ОК» на «Отмена»?конечно нет ... но после нажатия панель должна исчезнуть сама собой.И наоборот, если вы создадите панель для ввода пароля, представление не сможет провести процесс проверки.Это будет работа контроллера согласиться или нет на предоставленный пароль.
Хорошо, я думаю, вы поняли все MVC, теперь пришло время поговорить о том, как правильно объединить все эти компоненты:
Как представление должно уведомлять контроллер?На самом деле это зависит от языка, который вы используете.Сначала подумайте о следующем:
- Избегайте представления, чтобы узнать контроллер.
- Представление не должно извлекать информацию из контроллера или вызывать методы для него.Как он скажет контролеру что-то случилось?Через наблюдателей (или слушателей)
Маленькая скобка: представление может вызывать методы другого представления, если это просто вопрос поддержания его визуально правильным.Например, изменение размера окна обеспечит правильное отображение всех компонентов (подпредставлений) этих окон, но контроллер может не получить уведомление об изменении размера окна.
Связь между MVC - это ключ! Итак, как это работает?
Кто будет создавать и отображать представление?Контроллер!
Кто должен зарегистрироваться для просмотра событий?Контроллер тоже ... после создания
Как?Работа представления заключается в трансляции событий, когда они происходят.Обычно через ActionListener
в Java, наблюдатели в target-c, лямбда-функции c ++, обратный вызов функции в javascript.И контроллер, перечисленный в нем, реализующий правильную функцию «обратного вызова» actionPerformed
в Java, селектор или даже функцию обратного вызова, если вы предоставили функцию обратного вызова для представления.
Таким образом, когда представление, созданное из контроллера, запускает событие, контроллер уведомляется и должен будет обработать это событие.Если контроллер хочет изменить что-то в представлении, это довольно просто, потому что контроллер всегда владеет и знает представление.
Для каждого окна контроллер: это звучит хорошо, вы не обязаныиметь только один контроллер (ни одну модель или представление…) при создании экземпляра объекта контроллера входа, чтобы можно было также создать и отобразить окно входа в систему.
Один экземпляр модели наоказание услуг.Здесь представлен шаблон проектирования синглтона, вам, вероятно, понадобится только одна «модель аутентификации», потому что вы вошли в систему или вышли из нее в одном и том же приложении.Таким образом, вместо создания нескольких экземпляров модели входа в систему для каждого окна в зависимости от состояния входа в систему, вы фактически хотите иметь только один.Вы можете сделать это через статический экземпляр объекта модели входа в систему.Это называется синглтоном.
Теперь, как гарантировать, что то, что мы превращаем в модель, будет реплицировано на все контроллеры?Простой случай - один контроллер и одна модель.Контроллер меняет модель и ждет изменения модели, чтобы затем использовать изменение (например, чтобы отобразить его в виде).Что произойдет, если модель используется несколькими контроллерами?Например, модель представляет собой службу входа в систему, у вас есть 2 состояния: вошли в систему и вышли из системы.2 контроллера используют эту модель, каждый из этих контроллеров имеет свое собственное представление (окно), один просто отображает поле пароля, а второй - окно с изображением, которое отображается, только если пользователь вошел в систему.не одно решение этого дела, а несколько.Каждый использует образец слушания / наблюдения.Несколько контроллеров могут прослушивать изменения модели.Как только один из них меняет его, все контроллеры будут уведомлены об изменении и, следовательно, обновят представление.Другой способ - сохранить центр уведомлений (это другой шаблон) и позволить либо контроллеру, либо модели транслировать то, что произошло, все контроллеры, заинтересованные в этом событии, будут уведомлены об изменении и обработают событие.Это последнее решение особенно интересно для случаев, когда модель может измениться в любое время (конечно, через контроллер, спрятанный где-то).Центр уведомлений использует циклы событий и большую часть времени пытается уведомить основной цикл событий (который должен быть потоком ваших контроллеров пользовательского интерфейса)
Так что для нашего примера, если процесс входа в систему отключен, я буду использоватьпростой слушатель.Два контроллера прослушивают изменения модели.Один контроллер после ввода пароля вызовет модель метода login.loginWithPassword(String some password)
, если пароль в порядке, модель будет передавать событие «loginStateChanged».Все слушатели (включая наши 2 контроллера) получат это событие и сообщат представлению, что оно должно обновляться с тем, что мы хотим (например, отображать изображение, которое может отображаться только во время регистрации).
В примере выше, наш контроллер запрашивал модель, и модель напрямую запускает событие, это в потоке контроллера, что хорошо, потому что нет риска параллельного доступа (тот же поток = нет проблем):
Если бы логин был удаленным, например, аутентификация онлайн-сервиса, мы бы постучали в дверь сервера и подождали его ответа.Поскольку этот процесс может занять несколько секунд, контроллер будет застревать в ожидании ответа сервера.Чтобы избежать этого, мы должны затем отправить запрос в другой поток и просто изменить представление с сообщением (в ожидании сервера), пока мы не получим ответ от сервера.Как только мы получим ответ (или по истечении времени ожидания, если ответа не будет), созданная ранее нить получит ответ модели, являющийся истинным или ложным.Очень плохая идея - обновить представление напрямую с результатом.Проблема в том, что поток, получивший ответ, не работает в потоке основного контроллера, и поэтому, если мы обновим представление, мы столкнемся с параллельным доступом (если объект представления изменяется 2 потоками точно в одно и то же время, выстолкнуться с аварией).Один из способов убедиться, что мы не связываемся с основным потоком, - просто отправить ответ модели в основной поток через уведомление, которое будет отправлено в цикле основного события.В этом случае особенно я бы использовал центр уведомлений вместо простой трансляции из модели.Интересно отметить, что некоторые фреймворки обеспечивают трансляцию в основном цикле событий.Есть даже лучшее решение, использующее блочные или лямбда-функции, но оно выходит за рамки этого вопроса.
Подводя итог:
- Ни представление, ни модель не должны знать контроллер
- Только контроллер знает их обоих
- Обратите внимание на то, как они разговаривают друг с другом, используйте прослушиватели или уведомления, если имеется более 1 контроллера для одной модели.
- Процесс всегда следующий:
- Контроллер создает представление и прослушивает его
- Представление сообщает контроллеру, что произошло действие
- Процесс контроллера, который может запрашивать действиемодель что-то изменить
- Модель дает результат (либо возвращая функцию, через слушатель или обратный вызов)
- Контроллер обрабатывает результат
- Контроллер обновляет представлениес результатом.