Элегантный способ предотвратить круговые события в MVC? - PullRequest
7 голосов
/ 17 декабря 2008

Вопрос, вкратце:

В MVC, как вы различаете щелчок флажка (или изменение поля выбора или списка) от человеческого значения «Контроллер, измените модель», и щелчок флажка (или изменение окна выбора или изменения списка) от значения Контроллера «Я обновляю представление, потому что модель изменилась»?


Пример:

У меня есть приложение JS (все одна большая страница HTML + JS; за ним стоит сервер, и продолжается AJAX, но это не важно для примера), в котором есть понятие «Вершины», связанное «Краями». Пользовательский интерфейс позволяет добавлять и удалять вершины на карте, а также включать или отключать края между парами вершин.

Существует два способа отключить Edge от вершины A до вершины B:

  1. нажмите на Edge, чтобы в окне «Edge Details» появилась кнопка «Disable This Edge»; или
  2. нажмите на вершину A (или B), чтобы в окне «Детали вершины» появился контрольный список соседних вершин, из которого вы можете снять отметку с вершины B (или A).

Вот как это работает под капотом в MVC (, но смотрите обновление в конце этого поста, где я исправляю проблемы в моем понимании ):

  • Модель: список объектов Vertex и список объектов Edge.
  • Представление: пользовательский интерфейс GMaps с маркерами и полилиниями, а также флажками и кнопками, а также DIV «Vertex Details» и «Edge Details».
  • Контроллер:
    • JS-функции, которые обновляют модель при срабатывании событий на флажках и кнопках; и
    • Функции JS, которые обновляют представление при возникновении событий в моделях.

Вот особая нелегкость :

  1. Пользователь имеет окно сведений о вершине, ориентированное на вершину A, и окно сведений о ребре, ориентированное на край от вершины A до вершины B.
  2. Пользователь нажимает «Отключить этот край» в окне «Сведения о ребре».
  3. Функция контроллера 1 получает событие click и вызывает disable () для объекта модели Edge.
  4. Объект модели Edge запускает событие «Я только что отключился».
  5. Функция контроллера 2 получает событие «Я только что отключился», и
    1. перерисовывает окно сведений о крае, говоря: «Я отключен!» и
    2. снимает отметку с вершины B в окне сведений о вершине.
      1. Дерьмо! Это снова запускает функцию контроллера 1, которая прослушивала события пользовательского интерфейса, которые означают, что фронт был отключен!

Таким образом, происходит ненужное повторное обновление модели и повторное обновление представления. В более сложном представлении с событиями, которые инициируют события, которые инициируют события, это может привести к десяткам посторонних обновлений!


Обновление: отличный ответ.

Я немного неправильно понял MVC. У меня нет только одного вида, как я описал выше: у меня есть несколько видов на несколько моделей. В частности, у меня есть флажок View of Edges для определенного узла и отдельный «подробный стиль окна» View of Edge.

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

Таким образом, если каждое представление регистрируется для событий «состояние обновлено» в модели, и каждое представление обновляется при получении этих событий, тогда ответ на мой круговой вопрос о событиях будет следующим:

Представление списка флажков отключит события флажков в тот момент, когда оно обновляет флажки после изменения состояния модели.

Теперь, если пользователь отключает Edge через окно Edge Detail, Контроллер обновляет Edge Model, просмотр списка флажков получает уведомление об обновлении, а просмотр списка флажков достаточно умен, чтобы отключить события флажков при изменении статус соответствующего флажка.

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

Спасибо тем, кто ответил на мой вопрос!

Ответы [ 2 ]

3 голосов
/ 18 декабря 2008

Просто чтобы вспомнить модель MVC. Представления должны вообще обновляться. Вот как это работает: контроллер изменяет состояние модели, модель отправляет обновления для своих представлений, представления извлекают новое состояние из модели и обновляют себя. Хотя контроллеры и представления, как правило, связаны друг с другом (то есть детализация данных в графическом представлении), они никогда не должны взаимодействовать напрямую, только через модель. Это в общем конечно.

Таким образом, функции JS, которые обновляют ваши представления, на самом деле не являются контроллерами, что является важным отличием. Они должны рассматриваться как часть вашего взгляда. Это может быть не полезно для рассматриваемой проблемы, но я подумал, что это заслуживает указания.

Вы также не можете удалить свою модель, я полагаю, вы имеете в виду, что вы удаляете что-то из вашей модели, поскольку никакие представления или контроллеры не могут существовать (или находиться в функциональном состоянии), если они не поддерживается моделью.

Я не являюсь жокей JS-кода и не использую gmaps. Я не понимаю, в чем проблема. Приводит ли изменение состояния флажка (свойства флажка) к событию onClick ()? Это действительно не должно быть ИМХО, но, возможно, они реализовали это таким образом, в противном случае вы могли бы просто подключить свой контроллер к onClick () и добавить некоторую логику в флажок (или, где-то в JS, в функцию), чтобы изменить состояние флажка , Если это невозможно, лучше всего выбрать вариант 1 и 2.

дополнение: пользователь взаимодействует с представлением

Так что же происходит, когда пользователь хочет взаимодействовать с представлением? Часто виджет включает в себя как вид, так и контроллер. Флажок имеет вид (вы можете видеть, установлен он или нет), а также контроллер (вы можете щелкнуть по нему). При установке флажка в принципе должно произойти следующее:

  • Контроллер флажка получает событие
  • Контроллер флажка изменяет состояние для значения, которое этот флажок представляет в модели
  • слушателей обновлений модели (включая флажок)
  • флажок обновляет свой внешний вид, чтобы отразить, что это значение изменилось

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

0 голосов
/ 17 декабря 2008

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

С моей точки зрения, я бы посчитал нецелесообразным, чтобы Контроллер присоединялся к событию Click Edge, потому что он раскрывает слишком много подробностей о том, как Edge реализован и используется. Контроллер не заботится о том, как используется Edge, или о любых других деталях реализации.

Фактически, канонический стиль MVC вообще не требует, чтобы Контроллер перехватывал любые События Модели вообще, как правило, потому что состояние Модели не видоизменяется Представлением или любыми другими Контроллерами. Модель не обязана уведомлять контроллер о том, что он был изменен.

Чтобы исправить вашу проблему, вы должны определить интерфейс View, чтобы иметь один метод, такой как ToggleEdge:


public interface GraphView
{
    event Action ToggleEdge;
}

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

Когда вы реализуете интерфейс View в своем пользовательском интерфейсе, вы должны убедиться, что событие ToggleEdge вызывается из one местоположения. Вы можете сделать это, подключив событие Edge.Clicked в вашем View и используя его для переключения вашего флажка; это делает ваш флажок ответственным за подъем вентиляционного клапана до контроллера:


public class UI : UserControl, GraphView
{
    public event Action ToggleEdge;

    void OnToggleEdge(Edge edge)
    {
        if (ToggleEdge != null)
            ToggleEdge(edge);
    }

    protected void Edge_Clicked(object sender, EventArgs e)
    {
        CheckBox chkbox = FindCheckBoxThatCorrespondsToEdge((Edge)sender);
        chkbox.Checked = !chkbox.Checked;    
    }

    protected void chkEdge_CheckChanged(object sender, EventArgs e)
    {
        Edge edge = FindEdgeThatCorrespondsToCheckbox((CheckBox)sender);
        OnToggleEdge(edge);
    }
}

Вы можете привести аргумент, что View теперь знает слишком много о своей реализации: он знает, что ребра и флажки фундаментально связаны. Может быть, это еще один хак, но его, вероятно, можно отклонить, так как «логика пользовательского интерфейса» должна поддерживать синхронизацию дисплея View.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...