在 Android 上使用 VIPER 架構

jianhui_gan 7年前發布 | 11K 次閱讀 安卓開發 Android開發 移動開發

英文原文: 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 可以解決這個問題。

讓我們動手解決它!

帶著這些問題,我開始了一個新的 Android 項目,并決定使用 MVP + Interactor (或者你也可以叫它VIPE)。這樣我就可以把presenter中的某些職能移到Interactor中。 UI 事件處理以及為 View 準備來自Interactor的數據之類的事情留給presenter。然后 Interactor 只負責業務邏輯和獲取來自數據庫和 API 的數據。

另外,我使用接口來將不同的module連接在一起。這樣不同模塊之間的方法就互不干擾,有助于清晰的定義每個模塊的職責,避免程序員把邏輯放錯了地方。下面是接口的定義:

/*** 本文源碼為Kotlin ***/

class LoginContracts {
  interface View {
    fun goToHomeScreen(user: User)
    fun showError(message: String)
  }

  interface Presenter {
    fun onDestroy()
    fun onLoginButtonPressed(username: String, password: String)
  }

  interface Interactor {
    fun login(username: String, password: String)
  }

  interface InteractorOutput {
    fun onLoginSuccess(user: User)
    fun onLoginError(message: String)
  }
}

下面是實現了這些接口的類的代碼( Kotlin 寫的,但是Java類似)。

class LoginActivity: BaseActivity, LoginContracts.View {

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

  override fun onCreate() {
    //...
    loginButton.setOnClickListener { onLoginButtonClicked() }
  }

  override fun onDestroy() {
    presenter?.onDestroy()
    presenter = null
    super.onDestroy()
  }

  private fun onLoginButtonClicked() {
    presenter?.onLoginButtonClicked(usernameEditText.text, passwordEditText.text)
  }

  fun goToHomeScreen(user: User) {
    val intent = Intent(view, HomeActivity::class.java)
    intent.putExtra(Constants.IntentExtras.USER, user)
    startActivity(intent)
  }

  fun showError(message: String) {
    //shows the error on a dialog
  }
}

class LoginPresenter(var view: LoginContract.View?): LoginContract.Presenter, LoginContract.InteractorOutput {
    var interactor: LoginContract.Interactor? = LoginInteractor(this)

    fun onDestroy() {
      view = null
      interactor = null
    }

    fun onLoginButtonPressed(username: String, password: String) {
      interactor?.login(username, password)
    }

    fun onLoginSuccess(user: User) {
      view?.goToNextScreen(user)
    }

    fun onLoginError(message: String) {
      view?.showError(message)
    }
}

class LoginInteractor(var output: LoginContract.InteractorOutput?): LoginContract.Interactor {
  fun login(username: String, password: String) {
    LoginApiManager.login(username, password)
                ?.subscribeOn(Schedulers.newThread())
                ?.observeOn(AndroidSchedulers.mainThread())
                ?.subscribe({
                          //does something with the user, like saving it or the token
                          output?.onLoginSuccess(it)
                          },
                        { output?.onLoginError(it.message ?: "Error!") })
  }
}

完整的代碼在 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。

這里是 “VIPE” 和 VIPER之間的不同之處:

class LoginContracts {
  interface View {
    fun showError(message: String)
    //fun goToHomeScreen(user: User)     //這不再是View的職責的一部分 
  }

  interface Router {
    fun goToHomeScreen(user: User) // 現在由router來處理它 
  }
}

class LoginPresenter(var view: LoginContract.View?): LoginContract.Presenter, LoginContract.InteractorOutput {
    //now the presenter has a instance of the Router and passes the Activity to it on the constructor
    var router: LoginContract.Router? = LoginRouter(view as? Actiity)

    //...

    fun onLoginSuccess(user: User) {
      router?.goToNextScreen(user)
    }

    //...
}

class LoginRouter(var activity: Activity?) {
  fun goToHomeScreen(user: User) {
    val intent = Intent(view, HomeActivity::class.java)
    intent.putExtra(Constants.IntentExtras.USER, user)
    activity?.startActivity(intent)
  }
}

完整的代碼在 這里

現在我們把view的 routing 邏輯放到Router中。它只需要一個 Activity 的實例來調用 startActivity 方法。雖然我們仍然沒有像iOS VIPER 那樣把所有的東西都捆在一起,但至少遵循了單一職責原則。

總結

在使用MVP + Interactor開發了一個項目,以及幫助一個同事開發了一個完全的VIPER Android 項目之后,我可以負責人的說這個架構在 Android 上是可行的,也值得這樣去做。類變得更小更易維護。它還影響了開發進度,因為這個架構明確的告訴了你代碼該寫在什么地方。

在我司Cheesecake Lab,我們決定在大部分新項目中使用VIPER。這樣可以更高的維護項目,而且更易從一個iOS項目切換到Android項目,或者反過來。當然這是一個不斷演化的過程,不是一塵不變的。我們非常高興能得到你的反饋!

 

來自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0329/7754.html

 

 本文由用戶 jianhui_gan 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!