這篇博客主要的內容是譯自Göksel Köksal 的Blurring the Lines Between MVVM and VIPER (本文已得到做者的受權翻譯),我把本身對於業務架構模式觀點放在了文末,如下是譯文:ios
若是你開發過移動端App,那你確定據說過 MVVM 和 VIPER. 雖然有觀點說MVVM的擴展性不夠好,也有觀點說VIPER是個過分設計的產物。而我在這裏想說的是,它倆很是接近,甚至咱們都沒有必要去把它倆分開對待。程序員
先來快速地過一遍 MVVM 和 VIPER.swift
數據綁定
或者delegation
和blocks
實現.想了解更多關於這兩種架構的內容,能夠參考這篇牛逼的文章Bohdan Orlov: iOS Architecture Patterns*架構
首要的目標是將UI和業務邏輯分離。這樣才能夠在不破壞任何業務邏輯的狀況下去更新UI,或者單獨地去測試業務邏輯的代碼。事實上MVVM和VIPER均可以達到這個目標,只是方式不同而已。從這個角度來看的話,它倆的結構能夠像下面這樣: mvvm
MVVM的 UI 層只有一個 View 組件,而 VIPER 將 UI 層拆分紅了三個組件:View, Presenter 和 Router. 而業務層顯然二者基本差很少。 接下來咱們經過例子看看他倆在 UI 層的區別。假設咱們要用 MVVM 作一個簡單的 App: 把 IMDB 上 TOP 25 的電影數據拉下來並顯示在一個列表中。 組件代碼大概會是下面這樣:post
protocol MovieListView: MovieListViewModelDelegate {
private var viewModel: MovieListViewModel
func updateWithMovies(_ movies: [Movie])
func didTapOnReload()
func didTapOnMovie(at index: Int)
func showDetailView(for movie: Movie)
}
protocol MovieListViewModelDelegate: class {
func viewModelDidUpdate(_ model: MovieListViewModel)
}
protocol MovieListViewModel {
weak var delegate: MovieListViewModelDelegate? { get set }
var movies: [Movie] { get }
func fetchMovies()
}
複製代碼
fetchMovies
方法.updateWithMovies
並將電影對象轉化爲展現用的數據顯示到列表上。至關簡單的一個邏輯對吧。接下來咱們在 macOS 上建立一個基本相同的 App, 並儘量多地複用代碼。測試
首先能夠肯定一件事,view 的類確定是不同的。所以咱們無法複用 iOS App 中展現邏輯的代碼。而 iOS 的 view 已經在updateWithMovies
將電影對象轉化成了展現用的數據,因此想要複用這部分邏輯的就只能它抽出來。咱們把建立展現用的數據的代碼挪到一個介於 view 和 view model 之間的中間類裏, 這樣就能在 iOS 和 macOS 的 view 裏複用這部分代碼了。 因而咱們把這個中間類就叫 Presenter, 叫這個名字純屬偶然,和VIPER一毛關係都沒有~fetch
protocol MovieListView: MovieListPresenterDelegate {
private var presenter: MovieListPresenter
func didTapOnReload()
func didTapOnMovie(at index: Int)
func showDetailView(for movie: Movie)
}
protocol MovieListPresenterDelegate {
func updateWithMoviePresentations(_ movies: [MoviePresentation])
}
protocol MovieListPresenter: MovieListViewModelDelegate {
private var viewModel: MovieListViewModel
func reload()
func presentation(from movie: Movie) -> MoviePresentation
}
protocol MovieListViewModelDelegate: class {
func viewModelDidUpdate(_ model: MovieListViewModel)
}
protocol MovieListViewModel {
weak var delegate: MovieListViewModelDelegate? { get set }
var movies: [Movie] { get }
func fetchMovies()
}
複製代碼
reload
方法.fetchMovies
方法.updateWithMovies
並將電影對象轉化爲展現用的數據並通知 delegate(view).這意味着咱們能夠經過讓任何 view 遵循 MovieListView
協議就可以跨平臺實現上面的需求。 如今咱們經過複用 iOS 項目大部分的代碼實現了全新的 macOS App. 然而這個時候,蘋果宣佈了一個大事。。。spa
MovieListView
協議,而後綁定 presenter 就好了,簡直不要太簡單。
protocol MovieListView: MovieListPresenterDelegate {
...
func didTapOnMovie(at index: Int)
func showDetailView(for movie: Movie)
}
複製代碼
在實現這個新類的時候,咱們會意識到showDetailView
在新舊view的實現是同樣的。咱們可能會想到複製粘貼這部分代碼,不過咱們這麼優秀的工程師,怎麼可能容許複製粘貼代碼對吧? OK,咱們把這部分邏輯也挪出來,而且把這個組件叫 Router, 一樣,這個名字也是純屬偶然。翻譯
protocol MovieListRouter {
func showDetailView(for movie: Movie)
}
複製代碼
Router 做爲當前頁面的代言人,負責在須要的時候顯示對應的詳情頁。可是這個組件應該放在哪呢?放在新舊兩版view裏嗎?聽上去也能夠不過就以往經驗來看,除非確實需求發生變化,仍是不要頻繁改變 view 的代碼比較好。 仍是讓咱們把這個責任交給 presenter 吧,讓它來持有 router. 這樣當用戶行爲發生,presenter 接收到這個事件時,它能夠決定是調用 view model 來作計算仍是調用 router 來實現導航的功能。 如今咱們把導航的邏輯也複用了,能夠發版啦。 咱們一塊兒看看最終的代碼結構:
protocol MovieListView: MovieListPresenterDelegate {
private var presenter: MovieListPresenter
func didTapOnReload()
func didTapOnMovie(at index: Int)
}
protocol MovieListPresenterDelegate {
func updateWithMoviePresentations(_ movies: [MoviePresentation])
}
protocol MovieListPresenter: MovieListViewModelDelegate {
private var router: MovieListRouter
private var viewModel: MovieListViewModel
func reload()
func presentation(from movie: Movie) -> MoviePresentation
}
protocol MovieListRouter {
func showDetailView(for movie: Movie)
}
protocol MovieListViewModelDelegate: class {
func viewModelDidUpdate(_ model: MovieListViewModel)
}
protocol MovieListViewModel {
weak var delegate: MovieListViewModelDelegate? { get set }
var movies: [Movie] { get }
func fetchMovies()
}
複製代碼
看到這裏,我想你應該 get 到了吧,這時候咱們把 MovieListViewModel
更名爲 MovieListInteractor
的話, 代碼就變成了 100%的VIPER,但同時又沒有違背 MVVM 的原則。
軟件架構說白了就是一堆的規則。有的架構規則多,有的規則少。使用一種架構並不意味着就是徹底摒棄另一種。尤爲是當咱們在討論MVC, MVVM 和 VIPER的時候。
從左到右,是一個擴展性的演化,而不是先後矛盾。VIPER 是這三者當中的最細化的版本,這也是爲何不少人認爲它是設計過分了,並且事實上我也以爲這些人的的批評是對的。 VIPER一共有5個組件,然而你卻不必定在全部場景裏都須要所有的5個組件。我認爲咱們在開發過程當中應該把精力放在需求自己而不是盲目地去遵循一些設計規則。 對於 VIPER,個人建議是:關於VIPER,我在以前一直有所耳聞,可是由於沒有在項目中實踐過,對於細節其實是隻知其一;不知其二的。這篇文章從一個很是好的角度分析了VIPER和MVVM的區別,我看完後收益頗豐。所以在這裏將其翻譯爲中文,以便本身往後回顧。
對於架構模式,我本身的觀點,和文中的觀點很是相似,我認爲項目中選擇怎樣的架構模式根本不重要,咱們的目的只有一個,那就是解耦且易擴展。
被業界diss無數次的MVC,實際上在優秀的程序員手裏,照樣可以發揮得很好,可是到了一些相對初級的開發者那,則會有Massive Controller的問題,而這裏面最主要的緣由,我認爲就是MVC制定的規則太少了。
資深一些的開發者,他們對軟件架構的原則瞭解於心,所以不論架構模式的規則是多仍是少,從他們手中產出的代碼始終能維持在一個優雅的程度。所以,MVC在不一樣的人手中會有不一樣的結果。
而規則相對較多的MVVM,以及VIPER,在自身規則上作了更多的限制,使得不論什麼水平的開發者在遵循這些規則進行業務開發後,代碼質量可以保持在一個相對不錯的水平。
所以在我看來,選擇怎樣的架構模式取決於團隊的平均能力,大致上來講,團隊能力能夠和架構模式的規則數量成反比。
對於業務的架構模式有什麼問題,歡迎一塊兒討論。