实施MVP的最佳方式

时间:2017-09-01 12:11:11

标签: java design-patterns gwt mvp

在MVP模式中,视图和模型之间没有依赖关系。一切都由演示者完成。

在我们的GWT项目中,我们实现了GWT .page所示的所有MVP类。要访问该视图,我们的演示者使用如下界面:

public interface Display {
    HasClickHandlers getAddButton();
    HasClickHandlers getDeleteButton();
    ...
}

因此,演示者从视图中获取一个按钮,并将其点击处理程序应用于该视图。

但这对我来说是错误的。这违反了单一责任原则以及信息隐藏原则。演示者不应该知道视图内部的任何内容(即使我们得到一个抽象的HasClickHandlers)。

在我看来,我们应该更好地使用这种模式:

public interface Display {
    void setPresenter(Presenter p);
}

public interface Presenter {
    void onAdd();
    void onDelete();
}

因此,视图主动告诉演示者,发生了一些交互。但不是在哪个视图元素上。 我的团队合作伙伴争辩说,通过第一个解决方案,我们避免了循环依赖。那就对了。但无论如何,我更倾向于第二种方式,因为模块分离得更好,可以独立维护。

有哪些优点/缺点?

1 个答案:

答案 0 :(得分:3)

非常同意 - 如果您继续第二部分,那么您链接的文章将描述双方。

我没有构建我的视图公开窗口小部件或它们的HasSomethingHandlers - 我发现通常更好的构建Presenter API来公开这些可能的交互,原因如下:

  • 单元测试 - 制作模拟视图更简单,它可以调用方法而不是触发事件。这并不是说它是不可能的(特别是如果你的Presenter可以在JVM上运行,你可以为每个人创建一个代理类型。

  • 清理未使用的处理程序 - 演示者通常很便宜并且每次都会重新创建,但有时候视图很重并且保存,重用。每次重用视图时,都必须确保删除了所有这些外部添加的处理程序。这可以通过一些"生命周期"正确完成。诸如onStop()之类的方法或演示者上的某些东西,因此无论代码取代演示者的任何代码都可以将其关闭,但需要将其考虑在内。

  • 多视图实现 - 如单元测试,不同的视图实现(移动与桌面,只读与读写等)可能有不同的方式导致相同的更改。现在,您可以使用多种方法公开HasClickHandlers onAddClicked()HasTouchHandlers onAddTapped(),或者采用您描述的方法。 (您也可以将一些用户体验详细信息泄漏到演示者中,例如在最后一个字段中按Enter键,而不是单击按钮 - 可以说属于视图,而不是演示者)。

这种方法的一个缺点是:视图需要更多的大脑,而MVP的部分目标是尽可能保持视图的瘦。

最后,虽然这个替代方法在您链接的页面上没有进行实际比较和对比,但您链接的同一篇文章的第二页确实显示了您的方法http://www.gwtproject.org/articles/mvp-architecture-2.html

  

现在我们已经构建了UI,我们需要连接相关的UI事件,即添加/删除按钮点击,以及与联系人列表FlexTable的交互。这是我们开始注意到应用程序设计整体布局发生重大变化的地方。主要是因为我们希望通过UiHandler注释将视图中的方法链接到UI交互。第一个主要变化是我们希望ContactsPresenter实现一个Presenter接口,允许我们的ContactsView在收到点击,选择或其他事件时回调到演示者。 Presenter接口定义以下内容:

public interface Presenter<T> {
  void onAddButtonClicked();
  void onDeleteButtonClicked();
  void onItemClicked(T clickedItem);
  void onItemSelected(T selectedItem);
}

这是在描述构建视图的UiBinder方式的上下文中完成的,但是如果没有UiBinder,它同样适用。

因此,在讨论有关组织团队如何构建应用程序的文章时,请务必考虑整篇文章 - 这些文章都是从http://www.gwtproject.org/doc/latest/DevGuideMvpActivitiesAndPlaces.html引用的,这也是假设这种非事件处理方法。在那篇文章中还有其他的错误&#34;处理方式,如手动依赖注入,而不是像Gin或Dagger2这样的自动可靠工具。

这些文章并不是要描述一种真实的方式,而是要理解可以使用的思想和模式。不要冒这个货币,盲目地应用模式,但要确保理解利弊 - 并且作为一个团队工作,使用不一致的模式可能比没有模式更差!

编辑:我意识到我没有提到循环依赖问题!

这与你想要的一样多或少都是一个问题。如果你同时创建它们,并在构造函数中将引用传递给另一个,显然你有一个问题 - GWT无法代理它们(并且可以说这无论如何都是个坏主意)。但是,在未来的道路上,您可能会遇到这样的情况:构建视图的成本很高,并且可能会被池化/缓存而不是每次都重新创建,在这种情况下,视图需要被告知它现在可以使用的新演示者。

然后要求View界面支持void setPresenter(P)方法,所以我们的圈子被打破了。还要记住,这种方法要求演示者确保清除视图中的数据,要么知道何时设置新演示者以清除其自己的数据。

我的个人方法导致演示者拥有一个视图字段,在创建时注入(可能通过构造函数),并在演示者中使用start方法。调用时,演示者控制视图,准备好后,将其附加到dom层次结构中所属的位置。

是的,这在视图中需要一个额外的方法,但是如果你有任何基类用于你的视图,这将是微不足道的,如果你最终做任何视图重用,你仍然需要这个方法。