Статьи

Лучшая модель привязки данных

Если вы когда-либо реализовывали пользовательский интерфейс, вы, скорее всего, сталкивались с проблемой привязки данных и уведомления: когда значение в вашей модели изменяется, как вы обновляете свой пользовательский интерфейс, чтобы отражать новые значения?

В Java стандартным решением является использование PropertyChangeSupport и PropertyChangeListener, но я всегда находил этот подход очень громоздким и трудно масштабируемым. В этом посте я объясню почему и предложу то, что я считаю лучшим решением.

Проблема с PropertyChangeSupport

Вот основные проблемы с PropertyChangeSupport:

Экстремальный шаблон

Любой бин или класс модели, который хочет уведомить остальной мир, что его значение изменилось, должен включать следующее:

public class SomeBean {
 private PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);

 public void setProperty(String newValue) {
  String oldValue = property;
  property = newValue;
  changeSupport.firePropertyChange("propertyName", oldValue, newValue);
 }

 public void addPropertyChangeListener(PropertyChangeListener l) {
  changeSupport.add(l);
 }

 public void removePropertyChangeListener(PropertyChangeListener l) {
  changeSupport.remove(l);
 }
}

Это много кода для чего-то, что должно быть очень простым.

Обратите внимание, что этот код выглядит так, как будто он может быть кандидатом на черту (такую, какую вы найдете в Fantom или Scala). Черта (или сочетание) — это класс с несколькими ограничениями, которые другие классы могут «импортировать» напрямую в рамках своей реализации. Если бы мы определили черту PropertyChangeSupport, любой класс, импортирующий эту черту, автоматически предоставил бы три метода, определенных выше.

Проблема состоит в том, что большинство реализаций черт накладывают определенные ограничения на то, как вы можете указать свою черту, и одна из них заключается в том, что они обычно не могут переносить состояние (без полей). Поскольку поддержка изменения свойств обычно содержит по крайней мере один, если не два элемента состояния (объект поддержки изменений и, возможно, имя свойства), я не уверен, что черты здесь будут полезны. Во всяком случае, только языки, которые поддерживают множественное наследование реализаций, позволили бы сократить некоторые из этих стандартных примеров (например, C ++).

Экстремальное сцепление

Чтобы элемент GUI обновлялся при изменении значения модели, ему необходимо добавить себя в качестве прослушивателя этой модели. Например, вы можете представить, что если имя меняется в режиме, пользовательский интерфейс захочет обновить метку и заголовок окна. Если вы выполните математику, вы увидите, что «n» элементов пользовательского интерфейса, которые должны отслеживать «m» объекты модели, приведут к n * m связям. И, конечно же, ваша модель и ваш вид теперь очень тесно связаны, что добавляет еще больше проблем.

Экстремальное воздействие данных

Поскольку Java изначально не поддерживает свойства, вы не только сами указываете свои сеттеры вручную, но также должны помнить, что нужно запускать соответствующий PropertyChangeEvent при каждом изменении значения. Это не только подвержено ошибкам, оно также ставит вас перед дилеммой, когда вы решаете, хотите ли вы запускать такие события для объектов модели, которые не оказывают непосредственного влияния на графический интерфейс в данный момент (что, если они сделают в какой-то момент в будущем?) ,

Богоявление

Все эти соображения начали беспокоить меня совсем недавно, так как я был занят работой над плагином Eclipse. Я умножал количество объектов PropertyChangeSupport во всех моих моделях и в основном расширял один и тот же класс снова и снова (если мне повезло) или просто копировал / вставлял код выше (большую часть времени). Мне сильно не нравилось количество загрязнений, которое начало проникать в мой код, и я начал рассматривать альтернативы.

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

JBus и свойства

Моя реализация локальной шины очень мала и очень проста и похожа на ту, что я описал в посте блога, связанном выше: слушатели являются методами, и они объявляют себя с аннотацией @Subscriber. Предполагается, что эти методы принимают один параметр, представляющий класс интересующего их события.

Вот быстрый пример:

public class App {

  @Subscriber
  public void event1(NotifyEvent ne) {
    System.out.println("event1: " + ne);
  }

}
JBus mb = new JBus();
jb.register(new App());
jb.post(new NotifyEvent("notify"));

Это первая часть сантехники. Элементы GUI, которые хотят получать обновления, будут просто объявлять себя подписчиками для определенных типов событий (как правило, PropertyChangeEvent, но это не должно быть ограничено этим).

Вторая часть заключается в том, чтобы максимально автоматизировать публикацию событий, и в итоге я создал для этого класс Property. Вот пример того, как его использовать:

public class Person {
  // A property that the GUI is interested in
  private Property<String> name;

  // A field that the GUI is not interested in
  private int age;

  public void setName(String n) {
    name.set(n);
  }
}

Первое, что вы заметите, это то, что, поскольку они имеют тип Property, свойства, которые публикуют события, легко идентифицировать в коде. Кроме того, свойства будут автоматически публиковать события всякий раз, когда вызывается их установщик, что подразумевает, что 1) им нужно иметь экземпляр шины сообщений и 2) им нужно знать имя свойства, под которым будут опубликованы изменения.

Поэтому конструктор Property выглядит следующим образом:

public class Property<T> {
  public Property(JBus bus, String propertyName, Object source) { ... }
}

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

Выгоды

Как только я преобразовал свой код в использование JBus и Property, я заметил радикальное уменьшение кода котельной пластины и резкое увеличение гибкости. Всякий раз, когда появляется новая модель, заставить ее публиковать события тривиально. Всякий раз, когда я представляю новый графический элемент, который необходимо обновить самому себе, мне больше не нужно находить способ найти интересующий объект (ы) модели и добавить себя в качестве слушателя: все, что мне нужно, это имя свойства.

Ограничения

Вот несколько недостатков подхода JBus:

Автобус повсеместности

Чтобы этот подход работал, объекты модели и элементы графического интерфейса пользователя должны иметь указатель на шину сообщений, используемую для обмена событиями. В effet я преобразовываю граф зависимостей «m * n *» в граф «m + n», где все m и n зависят от шины сообщений. Автобус становится центральной частью, от которой зависят все события.

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

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

Название недвижимости

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

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

Производительность

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

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

Потенциальные улучшения

Хотя я начал реализовывать JBus в качестве универсальной инфраструктуры локальной шины, тот факт, что он настолько полезен в качестве механизма публикации изменений свойств, заставил меня задуматься, не следует ли мне предоставить какую-то специализацию для конкретных случаев использования, а именно для создания особых случаев для PropertyChangeEvent, что, вероятно, быть даже наиболее используемым.

Например, JBus позволяет публиковать события в определенных «категориях» (строках) и позволяет слушателям получать события только в том случае, если они заявили о заинтересованности в типе этого события * и * в том факте, что оно было опубликовано в той же категории. Я заметил, что часто удобно публиковать события изменения свойств, автоматически назначаемые категории, соответствующей имени свойства.

Интерфейсы против слушателей метода

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

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

 

От http://beust.com/weblog/2010/09/26/a-better-data-binding-model