Я использовал библиотеку 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, вам достаточно одного клика!