Я использовал библиотеку Vaadin 4 Spring в своем текущем проекте, и это было очень приятно. Однако в середине проекта мой коллега решил «улучшить тестируемость» . Намерение было похвальным, хотя проект уже пытался реализовать шаблон MVP (пожалуйста, проверьте эту статью для более подробной информации). Вместо того, чтобы исправлять ошибки здесь и там, он реорганизовал всю кодовую базу, используя предоставленный модуль MVP … ИМХО, это была огромная ошибка. В этой статье я попытаюсь осветить то, что мешает мне в существующей реализации, и альтернативное решение.
Существующая реализация MVP состоит из одного класса . Вот оно, сокращенно для удобства чтения:
public abstract class Presenter<V extends View> { @Autowired private SpringViewProvider viewProvider; @Autowired private EventBus eventBus; @PostConstruct protected void init() { eventBus.subscribe(this); } public V getView() { V result = null; Class<?> clazz = getClass(); if (clazz.isAnnotationPresent(VaadinPresenter.class)) { VaadinPresenter vp = clazz.getAnnotation(VaadinPresenter.class); result = (V) viewProvider.getView(vp.viewName()); } return result; } // Other plumbing code }
Этот класс довольно самоуверенный и имеет следующие недостатки:
- Он основан на автоматическом подключении в полевых условиях, что крайне затрудняет модульный тест классов Presenter . В качестве доказательства предоставленный тестовый класс является не модульным тестом, а интеграционным тестом .
- Он основан исключительно на компонентном сканировании, что предотвращает явное внедрение зависимостей.
- Он обеспечивает реализацию
View
интерфейса, независимо от того, требуется он или нет. Когда навигатор не используется, он делает реализацию пустогоenterView()
метода обязательной. - Он берет на себя ответственность за создание представления от поставщика представления.
- Он соединяет Presenter и View с его
@VaadinPresenter
аннотацией, не позволяя одному Presenter обрабатывать различные реализации View . - Это требует явного вызова
init()
метода Presenter , так как@PostConstruct
аннотация в суперклассе не вызывается, когда у подкласса есть такая.
Я разработал альтернативный класс, который пытается обратиться к предыдущим пунктам — и также проще:
public abstract class Presenter<T> { private final T view; private final EventBus eventBus; public Presenter(T view, EventBus eventBus) { Assert.notNull(view); Assert.notNull(eventBus); this.view = view; this.eventBus = eventBus; eventBus.subscribe(this); } // Other plumbing code }
Этот класс делает каждый подкласс легко тестируемым по модулю, как доказывают следующие фрагменты:
public class FooView extends Label {} public class FooPresenter extends Presenter<fooview> { public FooPresenter(FooView view, EventBus eventBus) { super(view, eventBus); } @EventBusListenerMethod public void onNewCaption(String caption) { getView().setCaption(caption); } } public class PresenterTest { private FooPresenter presenter; private FooView fooView; private EventBus eventBus; @Before public void setUp() { fooView = new FooView(); eventBus = mock(EventBus.class); presenter = new FooPresenter(fooView, eventBus); } @Test public void should_manage_underlying_view() { String message = "anymessagecangohere"; presenter.onNewCaption(message); assertEquals(message, fooView.getCaption()); } }
Тот же Интеграционный тест, что и для исходного класса, также может быть обработан с использованием явного внедрения зависимости:
public class ExplicitPresenter extends Presenter<FooView> { public ExplicitPresenter(FooView view, EventBus eventBus) { super(view, eventBus); } @EventBusListenerMethod public void onNewCaption(String caption) { getView().setCaption(caption); } } @Configuration @EnableVaadin public class ExplicitConfig { @Autowired private EventBus eventBus; @Bean @UIScope public FooView fooView() { return new FooView(); } @Bean @UIScope public ExplicitPresenter fooPresenter() { return new ExplicitPresenter(fooView(), eventBus); } } @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = ExplicitConfig.class) @VaadinAppConfiguration public class ExplicitPresenterIT { @Autowired private ExplicitPresenter explicitPresenter; @Autowired private EventBus eventBus; @Test public void should_listen_to_message() { String message = "message_from_explicit"; eventBus.publish(this, message); assertEquals(message, explicitPresenter.getView().getCaption()); } }
И последнее, но не менее важное: эта альтернатива также позволяет вам использовать автоматическое подключение и сканирование компонентов, если вам это нравится! Единственное отличие состоит в том, что он обеспечивает автоматическое подключение конструктора вместо полевого автоматического подключения (на мой взгляд, это считается плюсом, хотя и более многословным):
@UIScope @VaadinComponent public class FooView extends Label {} @UIScope @VaadinComponent public class AutowiredPresenter extends Presenter<FooView> { @Autowired public AutowiredPresenter(FooView view, EventBus eventBus) { super(view, eventBus); } @EventBusListenerMethod public void onNewCaption(String caption) { getView().setCaption(caption); } } @ComponentScan @EnableVaadin public class ScanConfig {} @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = ScanConfig.class) @VaadinAppConfiguration public class AutowiredPresenterIT { @Autowired private AutowiredPresenter autowiredPresenter; @Autowired private EventBus eventBus; @Test public void should_listen_to_message() { String message = "message_from_autowired"; eventBus.publish(this, message); assertEquals(message, autowiredPresenter.getView().getCaption()); } }
Хорошей новостью является то, что этот модуль теперь является частью проекта vaadin4spring на Github. Если вам нужно приложение MVP для приложения Vaadin Spring, вам достаточно одного клика!