不要再给MVP中的Presenter写接口了

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看303 回复回复1 收藏收藏 分享淘帖 转播转播 分享分享 微信
查看: 303|回复: 1
收起左侧

不要再给MVP中的Presenter写接口了

[复制链接]
安卓大师 发表于 2016-8-2 22:14:48 | 显示全部楼层 |阅读模式
快来登录
获取优质的苹果资讯内容
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
订阅梳理好了的知识点专辑
本帖最后由 安卓大师 于 2016-8-2 22:24 编辑

译者序:Android MVP模式中的Prensenter如何设计一直是人们关注的话题,我们之前分享过两篇关于MVP模式的文章:



不要再给MVP中的Presenter写接口了 1

不要再给MVP中的Presenter写接口了 - 敏捷大拇指 - 不要再给MVP中的Presenter写接口了 1


西班牙开发者antoiolg曾在GitHub上发过一个MVP实践,可以说是最早的优秀范例了。他让所有的Presenter都实现了接口,并在View层中坚持使用接口而不是实现类。几个月前Google发布了一个官方MVP示例项目,在项目中,Google首先写一个上帝接口BasePresenter,然后在每个功能模块里都写了协议类名为某某...Contract,在其中封装了模块下的View接口和Presenter接口,同时给View设定了泛型,就是当前协议类中Presenter:

[Java] 纯文本查看 复制代码
/**
 * 这个类声明了该模块下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继承接口。而本文观点正好相反,本文的观点不一定正确,但希望能引起你对这个问题的思考。

---------

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

这是MVP图解:

不要再给MVP中的Presenter写接口了 2

不要再给MVP中的Presenter写接口了 - 敏捷大拇指 - 不要再给MVP中的Presenter写接口了 2


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




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

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




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

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

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

[Java] 纯文本查看 复制代码
/**
* 登录用例。给出登录所需的邮箱和密码。
*/
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 {
  .........
}


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




3、为什么这里需要View接口?

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

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

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

[Java] 纯文本查看 复制代码
public interface LoginPresenter {
  void onLoginButtonPressed(String email, String password);
}

public class LoginPresenterImpl implements LoginPresenter {  
  ....
}


或者是这样:

[Java] 纯文本查看 复制代码
public interface ILoginPresenter {
  void onLoginButtonPressed(String email, String password);
}

public class LoginPresenter implements ILoginPresenter {  
  ....
}





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

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

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


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




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

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




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

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

我写了这篇文章就是为了讨论这个话题的。欢迎大家评论讨论

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




扩展阅读:

antoiolg的MVP实现:https://github.com/antoniolg/androidmvp
Google的MVP示例项目:https://github.com/googlesamples/android-architecture




相关内容

Android MVP架构中的Presentation层应该怎么设计

Android官方MVP架构示例项目解析

不要再给MVP中的Presenter写接口了




作者:Pedro Vicente Gómez Sánchez
译者:程大治

都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分