在 Android 上使用 VIPER 架构

英文原文:Using the VIPER architecture on Android 

我先是一个Android开发者,后来也做了iOS开发,接触过几种不同的架构 - 有好有坏。

在Android中我一直觉得MVP架构用着不错,直到在一个iOS的项目中遇到了VIPER架构,这个架构用了8个月。当我回到Android时,我决定采用这种设计,虽然有人建议说在Android上使用iOS的架构也许不合理,但我还是想在这个平台上实现VIPER。鉴于Android 和 iOS 框架之间的基本区别,我对 VIPER 为 Android 带来的实际用处存有疑问。这样做值得吗?让我们从基本概念开始。

什么是 VIPER?

VIPER 是一个主要用在iOS开发生的简明架构。它帮助保持代码的简洁有序,避免Massive-View-Controller的情况。

VIPER 是视图 (View),交互器 (Interactor),展示器 (Presenter),实体 (Entity) 以及路由 (Routing) 的首字母缩写,各个部分都有明确的职责,遵循单一职责原则。关于VIPER的更多知识,你可以查看这篇不错的文章

Android上的架构

Android上已经有一些非常不错的架构。最著名的就是  Model-View-ViewModel (MVVM) 和 Model-View-Presenter (MVP)

如果你和 data binding 一起使用,使用MVVM就很合理,因为我不是很喜欢 data binding 的理念(ps,译者倒是很喜欢的),所以一直在项目中使用MVP。但是随着项目的增长,presenter变成了一个方法超多的庞大的类,使得它很难维护和理解。因为它要负责许多事情:处理UI事件,UI逻辑,业务逻辑,网络和数据库查询。这违背了单一职责原则,而 VIPER 可以解决这个问题。

让我们动手解决它!

giphy-11.gif

带着这些问题,我开始了一个新的 Android 项目,并决定使用 MVP + Interactor (或者你也可以叫它VIPE)。这样我就可以把presenter中的某些职能移到Interactor中。 UI 事件处理以及为 View 准备来自Interactor的数据之类的事情留给presenter。然后 Interactor 只负责业务逻辑和获取来自数据库和 API 的数据。

另外,我使用接口来将不同的module连接在一起。这样不同模块之间的方法就互不干扰,有助于清晰的定义每个模块的职责,避免程序员把逻辑放错了地方。下面是接口的定义:

1.  /*** 本文源码为Kotlin ***/

2.   

3.  class LoginContracts {

4.    interface View {

5.      fun goToHomeScreen(user: User)

6.      fun showError(message: String)

7.    }

8.   

9.    interface Presenter {

10.     fun onDestroy()

11.     fun onLoginButtonPressed(username: String, password: String)

12.   }

13.  

14.   interface Interactor {

15.     fun login(username: String, password: String)

16.   }

17.  

18.   interface InteractorOutput {

19.     fun onLoginSuccess(user: User)

20.     fun onLoginError(message: String)

21.   }

22. }

 
  

下面是实现了这些接口的类的代码(Kotlin写的,但是Java类似)。

1.  class LoginActivity: BaseActivity, LoginContracts.View {

2.   

3.    var presenter: LoginContracts.Presenter? = LoginPresenter(this)

4.   

5.    override fun onCreate() {

6.      //...

7.      loginButton.setOnClickListener { onLoginButtonClicked() }

8.    }

9.   

10.   override fun onDestroy() {

11.     presenter?.onDestroy()

12.     presenter = null

13.     super.onDestroy()

14.   }

15.  

16.   private fun onLoginButtonClicked() {

17.     presenter?.onLoginButtonClicked(usernameEditText.text, passwordEditText.text)

18.   }

19.  

20.   fun goToHomeScreen(user: User) {

21.     val intent = Intent(view, HomeActivity::class.java)

22.     intent.putExtra(Constants.IntentExtras.USER, user)

23.     startActivity(intent)

24.   }

25.  

26.   fun showError(message: String) {

27.     //shows the error on a dialog

28.   }

29. }

30.  

31. class LoginPresenter(var view: LoginContract.View?): LoginContract.Presenter, LoginContract.InteractorOutput {

32.     var interactor: LoginContract.Interactor? = LoginInteractor(this)

33.  

34.     fun onDestroy() {

35.       view = null

36.       interactor = null

37.     }

38.  

39.     fun onLoginButtonPressed(username: String, password: String) {

40.       interactor?.login(username, password)

41.     }

42.  

43.     fun onLoginSuccess(user: User) {

44.       view?.goToNextScreen(user)

45.     }

46.  

47.     fun onLoginError(message: String) {

48.       view?.showError(message)

49.     }

50. }

51.  

52. class LoginInteractor(var output: LoginContract.InteractorOutput?): LoginContract.Interactor {

53.   fun login(username: String, password: String) {

54.     LoginApiManager.login(username, password)

55.                 ?.subscribeOn(Schedulers.newThread())

56.                 ?.observeOn(AndroidSchedulers.mainThread())

57.                 ?.subscribe({

58.                           //does something with the user, like saving it or the token

59.                           output?.onLoginSuccess(it)

60.                           },

61.                         { output?.onLoginError(it.message ?: "Error!") })

62.   }

63. }

 
  

完整的代码在this Gist

你可以看到modules是在开始的时候被创建和连接在一起的。当创建Activity的时候,它初始化了Presenter,把自己作为一个View传递给Presenter的构造函数。然后这个Presenter把自己作为InteractorOutput初始化Interactor。

而在一个iOS VIPER 项目中这应该是由Router来做的,创建UIViewController,或者从一个Storyboard获得它,然后把所有的module写在一起。但是在 Android 中我们不是自己创建Activity,而是通过Intent,我们无法从前一个Activity获取新建的Activity。这有助于避免内存泄漏,但是如果你想传递数据到新的模块就有点痛苦了。我们还不能把Presenter放到Intent的extra中,因为它需要是Parcelable 或者 Serializable的。

这就是为什么这个项目中我省略了Router。但是这是最佳选择吗?

VIPE + Router

前面VIPE的实现解决了MVP的绝大多数问题,用Interactor分离Presenter的职责。

但是,View并不像 iOS VIPER的View那样被动。它需要处理所有的常规职责以及导航到其它模块。这并不是它的工作,我们可以做的更好。我们要引入Router。

giphy1.gif

这里是 “VIPE” 和 VIPER之间的不同之处:

1. class LoginContracts {

2.   interface View {

3.     fun showError(message: String)

4.     //fun goToHomeScreen(user: User)     //这不再是View的职责的一部分 

5.   }

6.  

7.   interface Router {

8.     fun goToHomeScreen(user: User) // 现在由router来处理它 

9.   }

10.}

11. 

12.class LoginPresenter(var view: LoginContract.View?): LoginContract.Presenter, LoginContract.InteractorOutput {

13.    //now the presenter has a instance of the Router and passes the Activity to it on the constructor

14.    var router: LoginContract.Router? = LoginRouter(view as? Actiity)

15. 

16.    //...

17. 

18.    fun onLoginSuccess(user: User) {

19.      router?.goToNextScreen(user)

20.    }

21. 

22.    //...

23.}

24. 

25.class LoginRouter(var activity: Activity?) {

26.  fun goToHomeScreen(user: User) {

27.    val intent = Intent(view, HomeActivity::class.java)

28.    intent.putExtra(Constants.IntentExtras.USER, user)

29.    activity?.startActivity(intent)

30.  }

31.}

 
  

完整的代码在这里

现在我们把view的 routing 逻辑放到Router中。它只需要一个 Activity 的实例来调用 startActivity 方法。虽然我们仍然没有像iOS VIPER 那样把所有的东西都捆在一起,但至少遵循了单一职责原则。

总结

在使用MVP + Interactor开发了一个项目,以及帮助一个同事开发了一个完全的VIPER Android 项目之后,我可以负责人的说这个架构在 Android 上是可行的,也值得这样去做。类变得更小更易维护。它还影响了开发进度,因为这个架构明确的告诉了你代码该写在什么地方。

在我司Cheesecake Lab,我们决定在大部分新项目中使用VIPER。这样可以更高的维护项目,而且更易从一个iOS项目切换到Android项目,或者反过来。当然这是一个不断演化的过程,不是一尘不变的。我们非常高兴能得到你的反馈!

猜你喜欢

转载自blog.csdn.net/onlyou930/article/details/71036042