在 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 可以解決這個問題。
讓我們動手解決它!
帶著這些問題,我開始了一個新的 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