写点什么

不要再给 MVP 中 Prensenter 写接口了

  • 2016-08-22
  • 本文字数:2969 字

    阅读完需:约 10 分钟

译者序:有关是否要让 Presenter 实现接口这个问题并没有很多讨论。antoiolg 曾在 GitHub 上发过一个MVP 实践,最早的提交是在2014 年四月,可以说是最早的优秀范例了。他让所有的Presenter 都实现了接口,并在View 层中坚持使用接口而不是实现类。而几个月前Google 竟发布了官方MVP 实践。此码一放,众神退让。Google 的做法是:首先写一个上帝接口BasePresenter,然后在每个功能模块里都写了协议类名为…Contract,在其中封装了模块下的View 接口和Presenter 接口,同时给View 设定了泛型,就是当前协议类中Presenter:

复制代码
/**
* 这个类声明了该模块下 View 和 Presenter 的协议
* BaseView 和 BasePresenter 都是很简单的上帝接口
*/
public interface AddEditTaskContract {
interface View extends BaseView<Presenter> {
void showEmptyTaskError();
...
}
interface Presenter extends BasePresenter {
void saveTask(String title, String description);
...
}
}

这种管理方式的好处是,将 View 和 Presenter 管理起来,强化其一一对应的关系,便于操作。这也是原文没有提到的观点。总的来说,不论是否以协议类的方式呈现,现在开发者喜欢让 Presenter 继承接口。而原文观点相反。

原文链接: http://blog.karumi.com/interfaces-for-presenters-in-mvp-are-a-waste-of-time/

我们在 Karumi 已经说了很久的 MVP 了。今天,我们讨论的是是否需要给 MVP 中的 Presenter 写接口。

这是 MVP 图解:

在上面的图解中 Model 包含了所有实现业务逻辑的代码。Presenter 负责实现展示逻辑,View 是抽象化视图的接口。

为什么在这种模式下 View 需要用接口来实现?

因为我们想将 View 的实现解耦。我们需要将编写 Presentation 层的框架抽象化,使它没有外部依赖。如果需要,我们应可以很轻松的修改视图的具体实现。我们应当遵守依赖原则以便进行单元测试。请记住,为了遵守依赖原则,高层次的概念 - 比如Presenter 的实现,不能依赖任何低层次的细节,比如View 的具体实现。

为什么使用接口有益于进行单元测试?

因为为了编写单元测试,所有的代码都应该关联到你的域,而不是外部系统,比如SDK 或某个框架。

让我们通过一个Android 中登录界面的例子来解释。

复制代码
/**
* 登录用例。给出登录所需的邮箱和密码。
*/
public class Login {
private LoginService loginService;
public Login(LoginService loginService) {
this.loginService = loginService;
}
public void performLogin(String email, String password, LoginCallback callback) {
boolean loginSuccess = loginService.performLogin(email, password);
if (loginSuccess) {
callback.onLoginSuccess();
} else {
callback.onLoginError();
}
}
}
/**
* LoginPresenter,实现了和用户登录接口相关联的 Presentation 逻辑。
*/
public class LoginPresenter {
private LoginView view;
private Login login;
public LoginPresenter(LoginView view, Login login) {
this.view = view;
this.login = login;
}
public void onLoginButtonPressed(String email, String password) {
if (!areUserCredentialsValid(email, password)) {
view.showInvalidCredentialsMessage();
return;
}
login.performLogin(email, password, new LoginCallback {
void onLoginSuccess() {
view.showLoginSuccessMessage();
}
void onLoginError() {
view.showNetworkErrorMessage();
}
});
}
}
/**
* 声明了 Presenter 可以对 View 进行的操作,不依赖 View 具体实现,避免造成耦合。
*/
public interface LoginView {
void showLoginSuccessMessage()
void showInvalidCredentialsMessage()
void showNetworkErrorMessage()
}
public class LoginActivity extends Activity implements LoginView {
.........
}

请不要关注代码语法,这些都是代码片段,几乎可以说是伪代码了。

为什么这里需要 View 接口?

因为你需要在单元测试中用一个测试对象替代 View 的实现。那么为什么需要在单元测试中这么做呢?因为你可不想 mock 一个 Android SDK 然后在单元测试里使用 LoginActivity。要记住所有包含 Android SDK 的测试都不是单元测试。

一旦这里的实现清晰了,我们就需要一个接口,这样就无需依赖具体的实现了。

有的开发者还给 Presenter 设计了接口。如果我们继续按照上面的例子来写,那么实现会是这样:

复制代码
public interface LoginPresenter {
void onLoginButtonPressed(String email, String password);
}
public class LoginPresenterImpl implements LoginPresenter {
....
}

或者是这样:

复制代码
public interface ILoginPresenter {
void onLoginButtonPressed(String email, String password);
}
public class LoginPresenter implements ILoginPresenter {
....
}

这个多余的接口会造成什么问题?

恕我直言,这个接口并没有什么用处,它只是使整个开发过程更加复杂混乱。为什么这么说?

  • 看看类名。当接口是多余的时,所起的名字就会很奇怪,对代码也没有语义的价值。
  • 如果我们修改了 Presentation 逻辑,那么我们还需要修改这个接口。改好之后,我们才能更新实现。就算我们使用高级先进的 IDE,这还是很浪费时间。
  • 程序的走向很难把控。这是因为每当你在 Activity(View 的实现)中,想要进入 Presenter 时,你需要使用的是接口,但你常常想进入的是实现类。
  • 接口并没有提高项目的可测试性。Presenter 类可以通过任何 mocking 库由测试替身轻松替换,或是手动编写测试替身。我们总不能写一个依赖 Activity 并替换了 Presenter 的测试。

所以说,LoginPresenter 接口到底带来了什么呢?只有噪音啦。

我们应在何时使用接口呢?

当我们有多于一个实现时(在这里我们只有一个 Presenter 实现),我们应当使用接口。还有,当我们需要将我们的代码和某第三方库,比如某框架或 SDK 划清界线时也需要写接口。就算不使用接口,我们也可以使用 Composition 来生成抽象,但是直接使用 Java 接口自然轻松得多。我们建议在对某个概念有多个实现或是需要明确界线时使用接口。不然,还是不要添加多余代码了。记住接口的使用不是生成抽象、实现解耦的唯一方法。

那如果我想讲 View 实现与 Presenter 实现解耦呢?

你并不需要这样做。View 的实现是一个低层次的细节,Presenter 实现是一个高层次的抽象。实现细节可以依赖高层次抽象。你需要将你的域模型从执行框架中抽象出来,但你不需要反其道而行之。尝试对 View 实现与 Presenter 实现进行解耦只是浪费时间罢了。

我写了这个博客就是为了讨论这个话题的。欢迎大家评论讨论:)

PS:如果你在尝试 Android 应用和 Presenter 的另一种测试方式,我不建议你使用单元测试并使用测试替身替换 View。我更愿意使用这里所描述的方式,SUT 是整个Presentation 层,而不只是一个Presenter。(这里使用测试替身来替换用例)


感谢徐川对本文的策划和审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-08-22 17:316094

评论 1 条评论

发布
用户头像
请问,如果需要对presenter进行mock呢?
2019-09-03 09:53
回复
没有更多了
发现更多内容

ppt怎么画箭头?用这2个ppt工具轻松搞定绘图!

职场工具箱

效率工具 职场 办公软件 AIGC AI生成PPT

Navicat Premium 15 for Mac强大数据库管理软件

Mac相关知识分享

StarRocks 在 Shopee 数据产品的实践

StarRocks

Microsoft Remote Desktop Beta for Mac(微软远程连接工具)

Mac相关知识分享

SmartSVN for Mac(SVN客户端)

Mac相关知识分享

SecureCRT for mac强大的终端 SSH 仿真工具

Mac相关知识分享

MLP AI生态平台将掀起去中心化智能投资浪潮

股市老人

高通中国区董事长孟樸:5G与AI的融合正加速企业数字化转型步伐

新消费日报

语音 AI 迎来爆发期,也仍然隐藏着被低估的机会丨RTE2024 音频技术和 Voice AI 专场

声网

苹果电脑怎么玩CS MacBook怎么玩反恐精英?

阿拉灯神丁

CS 苹果电脑 CrossOver Mac下载 CrossOver 24

border使用以及单独方向设置

flfljh

PlatformView同层渲染方案适配切换指导

flfljh

为什么建议不要使用TikTok共享节点?

Ogcloud

TikTok tiktok运营 TikTok养号 tiktok矩阵 tiktok网络

TG机器人链游开发项目:迈向去中心化游戏新时代

区块链软件开发推广运营

交易所开发 dapp开发 链游开发 公链开发 代币开发

TikTok直播有什么要求?

Ogcloud

TikTok tiktok运营 tiktok直播 tiktok直播专线 tiktok直播网络

指标平台为业务部门提供实时、准确的数据支持,以助力业务决策

Aloudata

数据分析 指标管理 指标平台 指标开发

连续七年亮相进博会,高通携手合作伙伴共赢智能计算新时代

业界

AI 驱动设计仿真丨亿欧专访:Altair 打开工业软件解题新思路

Altair RapidMiner

人工智能 制造业 仿真 altair 工业软件

API网关如何在iPaaS平台中助企业构建安全高效的API生态体系

RestCloud

数字化转型 API API网关 ipaas

鸿蒙Flutter生成hap包编译过程可能遇到的问题

flfljh

Allavsoft for Mac(优秀的视频下载工具)

Mac相关知识分享

鸿蒙 next 实现做题每 10 题提交

flfljh

SpringBoot必须掌握的常用注解!

王磊

LeetCode题解:2665. 计数器 II

Lee Chen

DeFi 4.0峥嵘初现:主权金融时代的来临

区块链软件开发推广运营

交易所开发 dapp开发 链游开发 公链开发 代币开发

指标平台帮助企业在业务运营过程中快速定位和解决业务问题

Aloudata

数据仓库 数据分析 指标管理 指标平台 指标开发

2024(第一届)上饶市「游戏星火」创客挑战大赛全面启动!

Geek_2d6073

Eudic欧路词典 for Mac(英语词典翻译查询工具)

Mac相关知识分享

鸿蒙 next 使用并封装富文本hp-richtext

flfljh

harmony_flutter_amp 导入高德地图

flfljh

创新实践:基于边缘智能+扣子的智慧婴儿监控解决方案

火山引擎边缘云

物联网 大模型 AI 基础设施 边缘智能

不要再给MVP中Prensenter写接口了_Google_Pedro Vicente Gómez Sánchez_InfoQ精选文章