Статьи

GWT MVP сделано просто

GWT Model-View-Presenter — это шаблон проектирования для крупномасштабной разработки приложений. Будучи производным от MVC, он разделяет представление и логику и помогает создавать хорошо структурированный, легко тестируемый код. Чтобы помочь таким ленивым разработчикам, как я, я исследую, как уменьшить количество классов и интерфейсов для записи при использовании декларативных интерфейсов .

Классический MVP

Вы знаете, как разместить ссылку в Facebook? — Недавно мне пришлось создать эту функциональность для небольшого приложения для путешествий GWT.

Таким образом, вы можете ввести URL, который затем выбирается и анализируется. Вы можете выбрать одно из изображений на странице, просмотреть текст и, наконец, сохранить ссылку.
Теперь, как правильно настроить это в MVP? — Сначала вы создаете абстрактный интерфейс, напоминающий вид:

01
02
03
04
05
06
07
08
09
10
interface Display {
  HasValue<String> getUrl();
  void showResult();
  HasValue<String> getName();
  HasClickHandlers getPrevImage();
  HasClickHandlers getNextImage();
  void setImageUrl(String url);
  HasHTML getText();
  HasClickHandlers getSave();
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
class LinkView implements Display
  @UiField TextBox url;
  @UiField Label name;
  @UiField VerticalPanel result;
  @UiField Anchor prevImage;
  @UiField Anchor nextImage;
  @UiField Image image;
  @UiField HTML text;
  @UiField Button save;
  public HasValue<String> getUrl() {
    return url;
  }
  public void showResult() {
    result.setVisible(true);
  }
  // ... and so on ...
}

Затем докладчик получает доступ к представлению с помощью интерфейса, который по соглашению записывается внутри класса презентатора:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
class LinkPresenter
  interface Display {...};
  
  public LinkPresenter(final Display display) {
    display.getUrl().addValueChangeHandler(new ValueChangeHandler<String>() {
      @Override
      public void onValueChange(ValueChangeEvent<String> event) {
        Page page = parseLink(display.getUrl().getValue());
        display.getName().setValue(page.getTitle());
        // ...
        display.showResult();
      }
    });
   }
   // ... and so on ...
}

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

Упрощение

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class LinkPresenter
  @UiField HasValue<String> url;
  @UiField HasValue<String> name;
  @UiField VerticalPanel result;
  @UiField HasClickHandlers prevImage;
  @UiField HasClickHandlers nextImage;
  @UiField HasUrl image;
  @UiField HasHTML text;
  @UiField HasClickHandlers save;
  
  public LinkPresenter(final Display display) {
    url.addValueChangeHandler(new ValueChangeHandler<String>() {
      @Override
      public void onValueChange(ValueChangeEvent<String> event) {
        Page page = parseLink(url.getValue());
        name.setValue(page.getTitle());
        // ...
        result.setVisible(true);
      }
    });
   }
   // ... and so on ...
}

Поскольку можно объявлять внедренные элементы, используя их интерфейсы, у этого презентатора есть много преимуществ полноценного презентатора MVP: вы можете проверить это, установив компоненты реализации (см. Ниже), и вы можете легко изменить реализацию представлений.
Но теперь у вас есть все это в одном классе и одном файле view.ui.xml, и вы можете применять структурные изменения намного проще.

Создание абстрактных элементов пользовательского интерфейса

TextBox реализует HasValue <String>. Это просто Но как насчет свойств элементов пользовательского интерфейса, которые не доступны через интерфейсы? Примером, который вы, возможно, уже определили, является именованный результат VerticalPanel в приведенном выше коде и его метод setVisible (), который, к сожалению, реализован в базовом классе UiObject. Так что нет интерфейса, который мог бы, например. быть реализовано во время тестирования. Ради возможности переключения реализаций представления было бы лучше внедрить ComplexPanel, но даже это не может быть создано во время тестирования.

Единственным выходом в этом случае является создание нового интерфейса, скажем,

1
2
3
4
interface Visible {
  void setVisible(boolean visible);
  boolean isVisible();
}

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

1
2
3
4
package de.joergviola.gwt.tools;
class VisibleVerticalPanel
       extends VerticalPanel
       implements Visible {}

Это кажется утомительным и неоптимальным. Тем не менее, это должно быть сделано только для каждого компонента, а не для каждого вида, как в полном MVP, описанном выше.
Подождите — как использовать самодельные компоненты в шаблонах UiBuilder? — Это просто:

01
02
03
04
05
06
07
08
09
10
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:t="urn:import:de.joergviola.gwt.tools">
   <g:VerticalPanel width="100%">
    <g:TextBox styleName="big" ui:field="url" width="90%"/>
    <t:VisibleVerticalPanel ui:field="result"
                      visible="false"  width="100%">
    </t:VisibleVerticalPanel>
   </g:VerticalPanel>
</ui:UiBinder>

Объявление обработчиков

Стандартный способ объявления обработчиков (click-) очень удобен:

1
2
3
4
@UiHandler("login")
 public void login(ClickEvent event) {
  srv.login(username.getValue(), password.getValue());
 }

В упрощенном подходе MVP этот код будет находиться в презентаторе. Но параметр ClickEvent является компонентом View и может, например, не быть создан во время выполнения. С другой стороны, его нельзя удалить из подписи, потому что UiBuilder требует параметр Event.

Так что, к сожалению, нужно вернуться к регистрации ClickHandlers вручную (как и в любом случае в MVP):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public initWidget() {
       ...
       login.addClickHandler(new ClickHandler() {
               @Override
               public void onClick(ClickEvent event) {
                       login();
               }
       });
       ...
}
  
public void login(ClickEvent event) {
        srv.login(username.getValue(), password.getValue());
}

тестирование
Создание тестируемого приложения является одной из основных целей при внедрении MVP.
GwtTestCase может выполнять тесты в среде контейнера, но требует некоторого времени запуска. В TDD желательно иметь очень быстрые тесты, которые можно применять после каждого отдельного изменения без потери контекста.
Таким образом, MVP разработан, чтобы иметь возможность тестировать весь ваш код в стандартной JVM. В стандартном MVP вы создаете реализации интерфейсов представления. В этом упрощенном подходе достаточно создать реализации на уровне интерфейса компонента, как показано ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Value<T> implements HasValue<T> {
  
  private T value;
  List<ValueChangeHandler<T>> handlers =
                     new ArrayList<ValueChangeHandler<T>>();
  
  @Override
  public HandlerRegistration addValueChangeHandler(
    ValueChangeHandler<T> handler) {
   handlers.add(handler);
   return null;
  }
  
  @Override
  public void fireEvent(GwtEvent<?> event) {
   for (ValueChangeHandler<T> handler : handlers) {
    handler.onValueChange((ValueChangeEvent) event);
   }
  }
  
  @Override
  public T getValue() {
   return value;
  }
  
  @Override
  public void setValue(T value) {
   this.value = value;
  }
  
  @Override
  public void setValue(T value, boolean fireEvents) {
   if (fireEvents)
    ValueChangeEvent.fire(this, value);
   setValue(value);
  }
  
 }

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

Что ты выигрываешь?

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

Мне любопытно: расскажи мне свой опыт!

Справка: GWT MVP стало проще от нашего партнера JCG