【问题标题】:Best way to implement MVP [closed]实现 MVP 的最佳方法 [关闭]
【发布时间】:2017-09-01 12:11:11
【问题描述】:

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

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

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

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

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

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

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

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

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

有什么优点/缺点?

【问题讨论】:

  • 您的问题似乎与 StackOverflow 无关,因为它主要是基于意见的。您可能想查看Code review,但请确保您的问题首先适合那里。
  • 我想知道优点/缺点,以便更好地了解使用哪一个。
  • 是的,我明白这一点,但这样的问题在这里是题外话。请阅读Help page,了解哪些问题属于主题:“过于宽泛、不清楚、不完整或主要基于意见的问题可能会被社区搁置,直到它们得到改进。”

标签: java design-patterns gwt mvp


【解决方案1】:

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

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

  • 单元测试 - 创建一个调用方法而不是触发事件的模拟视图更简单。这并不是说不可能(尤其是如果您的 Presenter 可以在 JVM 上运行,并且您可以为每个人创建一个 Proxy 类型。

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

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

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

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

现在我们已经构建了 UI,我们需要关联相关的 UI 事件,即添加/删除按钮单击,以及与联系人列表 FlexTable 的交互。这是我们将开始注意到应用程序设计的整体布局发生重大变化的地方。主要是因为我们希望通过 UiHandler 注释将视图中的方法链接到 UI 交互。第一个主要变化是我们希望我们的 ContactsPresenter 实现一个 Presenter 接口,该接口允许我们的 ContactsView 在收到点击、选择或其他事件时回调到 Presenter。 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,这也假定了这种非事件处理程序方法。在那篇文章中还有其他“错误”的做事方式,比如手动依赖注入,而不是像 Gin 或 Dagger2 这样的自动化可靠工具。

这些文章不是为了描述一种真道,而是为了理解可以使用的想法和模式。不要盲目地应用模式,但请务必了解其优缺点 - 并作为一个团队工作,使用不一致的模式可以说比没有模式更糟糕!


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

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

那就要求 View 接口无论如何都支持void setPresenter(P) 方法,所以我们的圈子就坏了。还请记住,这种方法将要求演示者确保清除视图中的数据,或者视图知道何时设置新的演示者以清除其自己的数据。

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

是的,这需要视图中的一个额外方法,但是如果您的视图有任何基类,这将是微不足道的,如果您最终要进行任何视图重用,那么无论如何您都需要该方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-14
    相关资源
    最近更新 更多