我在当前项目中一直使用Vaadin 4 Spring库,这是非常愉快的体验。 但是,在项目中期,我的一位同事决定“提高可测试性” 。 尽管该项目已经尝试实现MVP模式,但此意图值得称赞(请查看本文以获得更多详细信息)。 他没有在这里和那里纠正错误,而是使用提供的MVP模块重构了整个代码库...恕我直言,这是一个巨大的错误。 在本文中,我将着重介绍在现有实现中令人烦恼的内容,以及它的替代解决方案。
现有的MVP实现由单个类组成 。 在这里,出于可读性目的而进行了删节:
publicabstractclassPresenter<VextendsView>{
@Autowired
privateSpringViewProviderviewProvider;
@Autowired
privateEventBuseventBus;
@PostConstruct
protectedvoidinit(){
eventBus.subscribe(this);
}
publicVgetView(){
Vresult=null;
Class<?>clazz=getClass();
if(clazz.isAnnotationPresent(VaadinPresenter.class)){
VaadinPresentervp=clazz.getAnnotation(VaadinPresenter.class);
result=(V)viewProvider.getView(vp.viewName());
}
returnresult;
}
// Other plumbing code
}
这门课很固执,有以下缺点:
- 它依赖于现场自动装配,这使得很难对Presenter类进行单元测试 。 作为证明,提供的测试类不是单元测试,而是集成测试 。
- 它仅依赖于组件扫描,这可以防止显式依赖项注入。
- 无论是否需要,它都会强制执行
View
接口。 不使用导航器时,它将强制执行空的enterView()
方法。 - 这需要建立从视图中提供的视图的责任。
- 它通过使用
@VaadinPresenter
注释将Presenter和View结合@VaadinPresenter
,防止单个Presenter处理不同的View实现。 - 它需要显式调用Presenter的
init()
方法,因为当子类有一个时,超类的@PostConstruct
注释不会被调用。
我开发了一个替代类,试图解决前面的要点-而且也更简单:
publicabstractclassPresenter<T>{
privatefinalTview;
privatefinalEventBuseventBus;
publicPresenter(Tview,EventBuseventBus){
Assert.notNull(view);
Assert.notNull(eventBus);
this.view=view;
this.eventBus=eventBus;
eventBus.subscribe(this);
}
// Other plumbing code
}
此类使每个子类都易于单元测试,如以下代码片段所示:
publicclassFooViewextendsLabel{}
publicclassFooPresenterextendsPresenter<FooView>{
publicFooPresenter(FooViewview,EventBuseventBus){
super(view,eventBus);
}
@EventBusListenerMethod
publicvoidonNewCaption(Stringcaption){
getView().setCaption(caption);
}
}
publicclassPresenterTest{
privateFooPresenterpresenter;
privateFooViewfooView;
privateEventBuseventBus;
@Before
publicvoidsetUp(){
fooView=newFooView();
eventBus=mock(EventBus.class);
presenter=newFooPresenter(fooView,eventBus);
}
@Test
publicvoidshould_manage_underlying_view(){
Stringmessage="anymessagecangohere";
presenter.onNewCaption(message);
assertEquals(message,fooView.getCaption());
}
}
还可以使用显式依赖项注入来处理与初始类相同的集成测试:
publicclassExplicitPresenterextendsPresenter<FooView>{
publicExplicitPresenter(FooViewview,EventBuseventBus){
super(view,eventBus);
}
@EventBusListenerMethod
publicvoidonNewCaption(Stringcaption){
getView().setCaption(caption);
}
}
@Configuration
@EnableVaadin
publicclassExplicitConfig{
@Autowired
privateEventBuseventBus;
@Bean
@UIScope
publicFooViewfooView(){
returnnewFooView();
}
@Bean
@UIScope
publicExplicitPresenterfooPresenter(){
returnnewExplicitPresenter(fooView(),eventBus);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ExplicitConfig.class)
@VaadinAppConfiguration
publicclassExplicitPresenterIT{
@Autowired
privateExplicitPresenterexplicitPresenter;
@Autowired
privateEventBuseventBus;
@Test
publicvoidshould_listen_to_message(){
Stringmessage="message_from_explicit";
eventBus.publish(this,message);
assertEquals(message,explicitPresenter.getView().getCaption());
}
}
最后但并非最不重要的一点是,如果您愿意,此替代方法还可以让您使用自动接线和组件扫描! 唯一的区别是,它强制执行构造函数自动装配而不是字段自动装配(在我看来,这算是一个加号,尽管有点冗长):
@UIScope
@VaadinComponent
publicclassFooViewextendsLabel{}
@UIScope
@VaadinComponent
publicclassAutowiredPresenterextendsPresenter<FooView>{
@Autowired
publicAutowiredPresenter(FooViewview,EventBuseventBus){
super(view,eventBus);
}
@EventBusListenerMethod
publicvoidonNewCaption(Stringcaption){
getView().setCaption(caption);
}
}
@ComponentScan
@EnableVaadin
publicclassScanConfig{}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ScanConfig.class)
@VaadinAppConfiguration
publicclassAutowiredPresenterIT{
@Autowired
privateAutowiredPresenterautowiredPresenter;
@Autowired
privateEventBuseventBus;
@Test
publicvoidshould_listen_to_message(){
Stringmessage="message_from_autowired";
eventBus.publish(this,message);
assertEquals(message,autowiredPresenter.getView().getCaption());
}
}
好消息是,此模块现已成为Github上vaadin4spring项目的一部分。 如果您的Vaadin Spring应用程序需要MVP,只需单击即可!
翻译自: https://blog.frankel.ch/improving-the-vaadin-4-spring-project-with-a-simpler-mvp/