iOS 架構(gòu)模式--解密 MVC苫昌,MVP颤绕,MVVM以及VIPER架構(gòu)

本文由CocoaChina譯者lynulzy(社區(qū)ID)翻譯
作者:Bohdan Orlov
原文:iOS Architecture Patterns

為什么要關(guān)注架構(gòu)設(shè)計?

因為假如你不關(guān)心架構(gòu)祟身,那么總有一天奥务,需要在同一個龐大的類中調(diào)試若干復(fù)雜的事情,你會發(fā)現(xiàn)在這樣的條件下袜硫,根本不可能在這個類中快速的找到以及有效的修改任何bug.當(dāng)然氯葬,把這樣的一個類想象為一個整體是困難的,因此婉陷,有可能一些重要的細(xì)節(jié)總會在這個過程中會被忽略帚称。如果現(xiàn)在的你正是處于這樣一個開發(fā)環(huán)境中,很有可能具體的情況就像下面這樣:

這個類是一個UIViewController的子類
數(shù)據(jù)直接在UIViewController中存儲
UIView類幾乎不做任何事情
Model 僅僅是一個數(shù)據(jù)結(jié)構(gòu)
單元測試覆蓋不了任何用例
以上這些情況仍舊會出現(xiàn)秽澳,即使是你遵循了Apple的指導(dǎo)原則并且實現(xiàn)了其 MVC(模式闯睹,所以,大可不必驚慌担神。Apple所提出的 MVC 模式存在一些問題瞻坝,我們之后會詳述。

在此杏瞻,我們可以定義一個好的架構(gòu)應(yīng)該具備的特點:

任務(wù)均衡分?jǐn)偨o具有清晰角色的實體
可測試性通常都來自與上一條(對于一個合適的架構(gòu)是非常容易)
易用性和低成本維護(hù)
為什么采用分布式?

采用分布式可以在我們要弄清楚一些事情的原理時保持一個均衡的負(fù)載。如果你認(rèn)為你的開發(fā)工作越多衙荐,你的大腦越能習(xí)慣復(fù)雜的思維捞挥,其實這是對的。但是忧吟,不能忽略的一個事實是砌函,這種思維能力并不是線性增長的,而且也并不能很快的到達(dá)峰值。所以讹俊,能夠戰(zhàn)勝這種復(fù)雜性的最簡單的方法就是在遵循 單一功能原則 的前提下垦沉,將功能劃分給不同的實體。

為什么需要易測性仍劈?

其實這條要求對于哪些習(xí)慣了單元測試的人并不是一個問題厕倍,因為在添加了新的特性或者要增加一些類的復(fù)雜性之后通常會失效。這就意味著贩疙,測試可以避免開發(fā)者在運行時才發(fā)現(xiàn)問題----當(dāng)應(yīng)用到達(dá)用戶的設(shè)備讹弯,每一次維護(hù)都需要浪費長達(dá)至少一周的時間才能再次分發(fā)給用戶。

為什么需要易用性这溅?

這個問題沒有固定的答案组民,但值得一提的是,最好的代碼是那些從未寫過的代碼悲靴。因此臭胜,代碼寫的越少,Bug就越少癞尚。這意味著希望寫更少的代碼不應(yīng)該被單純的解釋為開發(fā)者的懶惰耸三,而且也不應(yīng)該因為偏愛更聰明的解決方案而忽視了它的維護(hù)開銷。

MV(X)系列概要

當(dāng)今我們已經(jīng)有很架構(gòu)設(shè)計模式方面的選擇:

![Uploading 1452152223816977_102409.png . . .]
MVC
MVP
MVVM
VIPER
前三種設(shè)計模式都把一個應(yīng)用中的實體分為以下三類:

Models--負(fù)責(zé)主要的數(shù)據(jù)或者操作數(shù)據(jù)的數(shù)據(jù)訪問層否纬,可以想象 Perspn 和 PersonDataProvider 類吕晌。
Views--負(fù)責(zé)展示層(GUI),對于iOS環(huán)境可以聯(lián)想一下以 UI 開頭的所有類临燃。
Controller/Presenter/ViewModel--負(fù)責(zé)協(xié)調(diào) Model 和 View睛驳,通常根據(jù)用戶在View上的動作在Model上作出對應(yīng)的更改,同時將更改的信息返回到View上膜廊。
將實體進(jìn)行劃分給我們帶來了以下好處:

更好的理解它們之間的關(guān)系
復(fù)用(尤其是對于View和Model)
獨立的測試
讓我們開始了解MV(X)系列乏沸,之后再返回到VIPER模式。

MVC的過去

在我們探討Apple的MVC模式之前爪瓜,我們來看下傳統(tǒng)的MVC模式蹬跃。


傳統(tǒng)的MVC

在這里,View并沒有任何界限铆铆,僅僅是簡單的在Controller中呈現(xiàn)出Model的變化蝶缀。想象一下,就像網(wǎng)頁一樣薄货,在點擊了跳轉(zhuǎn)到某個其他頁面的連接之后就會完全的重新加載頁面翁都。盡管在iOS平臺上實現(xiàn)這這種MVC模式是沒有任何難度的,但是它并不會為我們解決架構(gòu)問題帶來任何裨益谅猾。因為它本身也是柄慰,三個實體間相互都有通信鳍悠,而且是緊密耦合的。這很顯然會大大降低了三者的復(fù)用性坐搔,而這正是我們不愿意看到的藏研。鑒于此我們不再給出例子。

“傳統(tǒng)的MVC架構(gòu)不適用于當(dāng)下的iOS開發(fā)”

蘋果推薦的MVC--愿景

Cocoa MVC

由于Controller是一個介于View 和 Model之間的協(xié)調(diào)器概行,所以View和Model之間沒有任何直接的聯(lián)系蠢挡。Controller是一個最小可重用單元,這對我們來說是一個好消息占锯,因為我們總要找一個地方來寫邏輯復(fù)雜度較高的代碼袒哥,而這些代碼又不適合放在Model中。

理論上來講消略,這種模式看起來非常直觀堡称,但你有沒有感到哪里有一絲詭異?你甚至聽說過艺演,有人將MVC的縮寫展開成(Massive View Controller)却紧,更有甚者,為View controller減負(fù)也成為iOS開發(fā)者面臨的一個重要話題胎撤。如果蘋果繼承并且對MVC模式有一些進(jìn)展晓殊,所有這些為什么還會發(fā)生?

蘋果推薦的MVC--事實

Realistic Cocoa MVC

Cocoa的MVC模式驅(qū)使人們寫出臃腫的視圖控制器伤提,因為它們經(jīng)常被混雜到View的生命周期中巫俺,因此很難說View和ViewController是分離的。盡管仍可以將業(yè)務(wù)邏輯和數(shù)據(jù)轉(zhuǎn)換到Model肿男,但是大多數(shù)情況下當(dāng)需要為View減負(fù)的時候我們卻無能為力了介汹,View的最大的任務(wù)就是向Controller傳遞用戶動作事件。ViewController最終會承擔(dān)一切代理和數(shù)據(jù)源的職責(zé)舶沛,還負(fù)責(zé)一些分發(fā)和取消網(wǎng)絡(luò)請求以及一些其他的任務(wù)嘹承,因此它的名字的由來...你懂的。

你可能會看見過很多次這樣的代碼:

var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
userCell.configureWithUser(user)

這個cell,正是由View直接來調(diào)用Model如庭,所以事實上MVC的原則已經(jīng)違背了叹卷,但是這種情況是一直發(fā)生的甚至于人們不覺得這里有哪些不對。如果嚴(yán)格遵守MVC的話坪它,你會把對cell的設(shè)置放在 Controller 中骤竹,不向View傳遞一個Model對象,這樣就會大大增加Controller的體積往毡。

“Cocoa 的MVC被寫成Massive View Controller 是不無道理的瘤载。”

直到進(jìn)行單元測試的時候才會發(fā)現(xiàn)問題越來越明顯卖擅。因為你的ViewController和View是緊密耦合的,對它們進(jìn)行測試就顯得很艱難--你得有足夠的創(chuàng)造性來模擬View和它們的生命周期,在以這樣的方式來寫View Controller的同時惩阶,業(yè)務(wù)邏輯的代碼也逐漸被分散到View的布局代碼中去挎狸。

我們看下一些簡單的例子:

import UIKit
 
struct Person { // Model
    let firstName: String
    let lastName: String
}
 
class GreetingViewController : UIViewController { // View + Controller
    var person: Person!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
     
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
     
    func didTapButton(button: UIButton) {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.greetingLabel.text = greeting
         
    }
    // layout code goes here
}
// Assembling of MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;

“MVC可以在一個正在顯示的ViewController中實現(xiàn)”

這段代碼看起來可測試性并不強,我們可以把和greeting相關(guān)的都放到GreetingModel中然后分開測試断楷,但是這樣我們就無法通過直接調(diào)用在GreetingViewController中的UIView的方法(viewDidLoad和didTapButton方法)來測試頁面的展示邏輯了锨匆,因為一旦調(diào)用則會使整個頁面都變化,這對單元測試來講并不是什么好消息冬筒。

事實上恐锣,在單獨一個模擬器中(比如iPhone 4S)加載并測試UIView并不能保證在其他設(shè)備中也能正常工作,因此我建議在單元測試的Target的設(shè)置下移除"Host Application"項舞痰,并且不要在模擬器中測試你的應(yīng)用土榴。

“View和Controller的接口并不適合單元測試∠炫#”

以上所述玷禽,似乎Cocoa MVC 看起來是一個相當(dāng)差的架構(gòu)方案。我們來重新評估一下文章開頭我們提出的MVC一系列的特征:

任務(wù)均攤--View和Model確實是分開的呀打,但是View和Controller卻是緊密耦合的
可測試性--由于糟糕的分散性矢赁,只能對Model進(jìn)行測試
易用性--與其他幾種模式相比最小的代碼量。熟悉的人很多贬丛,因而即使對于經(jīng)驗不那么豐富的開發(fā)者來講維護(hù)起來也較為容易撩银。
如果你不想在架構(gòu)選擇上投入更多精力,那么Cocoa MVC無疑是最好的方案豺憔,而且你會發(fā)現(xiàn)一些其他維護(hù)成本較高的模式對于你所開發(fā)的小的應(yīng)用是一個致命的打擊额获。

“就開發(fā)速度而言,Cocoa MVC是最好的架構(gòu)選擇方案焕阿∵浞龋”

MVP 實現(xiàn)了Cocoa的MVC的愿景

Passive View variant of MVP

這看起來不正是蘋果所提出的MVC方案嗎?確實是的暮屡,這種模式的名字叫做MVC撤摸,但是,這就是說蘋果的MVC實際上就是MVP了褒纲?不准夷,并不是這樣的。如果你仔細(xì)回憶一下莺掠,View是和Controller緊密耦合的衫嵌,但是MVP的協(xié)調(diào)器Presenter并沒有對ViewController的生命周期做任何改變,因此View可以很容易的被模擬出來彻秆。在Presenter中根本沒有和布局有關(guān)的代碼楔绞,但是它卻負(fù)責(zé)更新View的數(shù)據(jù)和狀態(tài)结闸。

但是,“假如告訴你UIViewController就是View呢酒朵?”

就MVP而言桦锄,UIViewController的子類實際上就是Views并不是Presenters。這點區(qū)別使得這種模式的可測試性得到了極大的提高蔫耽,付出的代價是開發(fā)速度的一些降低结耀,因為必須要做一些手動的數(shù)據(jù)和事件綁定,從下例中可以看出:

import UIKit
 
struct Person { // Model
    let firstName: String
    let lastName: String
}
 
protocol GreetingView: class {
    func setGreeting(greeting: String)
}
 
protocol GreetingViewPresenter {
    init(view: GreetingView, person: Person)
    func showGreeting()
}
 
class GreetingPresenter : GreetingViewPresenter {
    unowned let view: GreetingView
    let person: Person
    required init(view: GreetingView, person: Person) {
        self.view = view
        self.person = person
    }
    func showGreeting() {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.view.setGreeting(greeting)
    }
}
 
class GreetingViewController : UIViewController, GreetingView {
    var presenter: GreetingViewPresenter!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
     
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
     
    func didTapButton(button: UIButton) {
        self.presenter.showGreeting()
    }
     
    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
     
    // layout code goes here
}
// Assembling of MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter

關(guān)于整合問題的重要說明

MVP是第一個如何協(xié)調(diào)整合三個實際上分離的層次的架構(gòu)模式匙铡,既然我們不希望View涉及到Model图甜,那么在顯示的View Controller(其實就是View)中處理這種協(xié)調(diào)的邏輯就是不正確的,因此我們需要在其他地方來做這些事情鳖眼。例如黑毅,我們可以做基于整個App范圍內(nèi)的路由服務(wù),由它來負(fù)責(zé)執(zhí)行協(xié)調(diào)任務(wù)具帮,以及View到View的展示博肋。這個出現(xiàn)并且必須處理的問題不僅僅是在MVP模式中,同時也存在于以下集中方案中蜂厅。

我們來看下MVP模式下的三個特性的分析:

任務(wù)均攤--我們將最主要的任務(wù)劃分到Presenter和Model匪凡,而View的功能較少(雖然上述例子中Model的任務(wù)也并不多)。
可測試性--非常好掘猿,由于一個功能簡單的View層病游,所以測試大多數(shù)業(yè)務(wù)邏輯也變得簡單
易用性--在我們上邊不切實際的簡單的例子中,代碼量是MVC模式的2倍稠通,但同時MVP的概念卻非常清晰
“iOS 中的MVP意味著可測試性強衬衬、代碼量大「拈伲”

MVP--綁定和信號

還有一些其他形態(tài)的MVP--監(jiān)控控制器的MVP滋尉。

這個變體包含了View和Model之間的直接綁定,但是Presenter仍然來管理來自View的動作事件飞主,同時也能勝任對View的更新狮惜。


Supervising Presenter variant of the MVP

但是我們之前就了解到,模糊的職責(zé)劃分是非常糟糕的碌识,更何況將View和Model緊密的聯(lián)系起來碾篡。這和Cocoa的桌面開發(fā)的原理有些相似。

和傳統(tǒng)的MVC一樣筏餐,寫這樣的例子沒有什么價值开泽,故不再給出。

MVVM--最新且是最偉大的MV(X)系列的一員

MVVM架構(gòu)是MV(X)系列最新的一員魁瞪,因此讓我們希望它已經(jīng)考慮到MV(X)系列中之前已經(jīng)出現(xiàn)的問題穆律。

從理論層面來講MVVM看起來不錯惠呼,我們已經(jīng)非常熟悉View和Model,以及Meditor峦耘,在MVVM中它是View Model罢杉。

MVVM

它和MVP模式看起來非常像:

MVVM將ViewController視作View
在View和Model之間沒有緊密的聯(lián)系
此外,它還有像監(jiān)管版本的MVP那樣的綁定功能贡歧,但這個綁定不是在View和Model之間而是在View和ViewModel之間。

那么問題來了赋秀,在iOS中ViewModel實際上代表什么利朵?它基本上就是UIKit下的每個控件以及控件的狀態(tài)。ViewModel調(diào)用會改變Model同時會將Model的改變更新到自身并且因為我們綁定了View和ViewModel猎莲,第一步就是相應(yīng)的更新狀態(tài)绍弟。

綁定

我在MVP部分已經(jīng)提到這點了,但是該部分我們?nèi)詴^續(xù)討論著洼。

如果我們自己不想自己實現(xiàn)樟遣,那么我們有兩種選擇:

基于KVO的綁定庫如 RZDataBinding 和 SwiftBond
完全的函數(shù)響應(yīng)式編程,比如像ReactiveCocoa身笤、RxSwift或者 PromiseKit
事實上豹悬,尤其是最近,你聽到MVVM就會想到ReactiveCoca液荸,反之亦然瞻佛。盡管通過簡單的綁定來使用MVVM是可實現(xiàn)的,但是ReactiveCocoa卻能更好的發(fā)揮MVVM的特點娇钱。

但是關(guān)于這個框架有一個不得不說的事實:強大的能力來自于巨大的責(zé)任伤柄。當(dāng)你開始使用Reactive的時候有很大的可能就會把事情搞砸。換句話來說就是文搂,如果發(fā)現(xiàn)了一些錯誤适刀,調(diào)試出這個bug可能會花費大量的時間,看下函數(shù)調(diào)用棧:

Reactive Debugging

在我們簡單的例子中煤蹭,F(xiàn)RF框架和KVO被過渡禁用笔喉,取而代之地我們直接去調(diào)用showGreeting方法更新ViewModel,以及通過greetingDidChange 回調(diào)函數(shù)使用屬性疯兼。

import UIKit
 
struct Person { // Model
    let firstName: String
    let lastName: String
}
 
protocol GreetingViewModelProtocol: class {
    var greeting: String? { get }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change
    init(person: Person)
    func showGreeting()
}
 
class GreetingViewModel : GreetingViewModelProtocol {
    let person: Person
    var greeting: String? {
        didSet {
            self.greetingDidChange?(self)
        }
    }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
    required init(person: Person) {
        self.person = person
    }
    func showGreeting() {
        self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
    }
}
 
class GreetingViewController : UIViewController {
    var viewModel: GreetingViewModelProtocol! {
        didSet {
            self.viewModel.greetingDidChange = { [unowned self] viewModel in
                self.greetingLabel.text = viewModel.greeting
            }
        }
    }
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
     
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)
    }
    // layout code goes here
}
// Assembling of MVVM
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel

讓我們再來看看關(guān)于三個特性的評估:

任務(wù)均攤 -- 在例子中并不是很清晰然遏,但是事實上,MVVM的View要比MVP中的View承擔(dān)的責(zé)任多吧彪。因為前者通過ViewModel的設(shè)置綁定來更新狀態(tài)待侵,而后者只監(jiān)聽Presenter的事件但并不會對自己有什么更新。
可測試性 -- ViewModel不知道關(guān)于View的任何事情姨裸,這允許我們可以輕易的測試ViewModel秧倾。同時View也可以被測試怨酝,但是由于屬于UIKit的范疇,對他們的測試通常會被忽略那先。
易用性 -- 在我們例子中的代碼量和MVP的差不多农猬,但是在實際開發(fā)中,我們必須把View中的事件指向Presenter并且手動的來更新View售淡,如果使用綁定的話斤葱,MVVM代碼量將會小的多。
“MVVM很誘人揖闸,因為它集合了上述方法的優(yōu)點揍堕,并且由于在View層的綁定,它并不需要其他附加的代碼來更新View汤纸,盡管這樣衩茸,可測試性依然很強≈ⅲ”

VIPER--把LEGO建筑經(jīng)驗遷移到iOS app的設(shè)計

VIPER是我們最后要介紹的楞慈,由于不是來自于MV(X)系列,它具備一定的趣味性啃擦。

迄今為止囊蓝,劃分責(zé)任的粒度是很好的選擇。VIPER在責(zé)任劃分層面進(jìn)行了迭代议惰,VIPER分為五個層次:

VIPER

交互器 -- 包括關(guān)于數(shù)據(jù)和網(wǎng)絡(luò)請求的業(yè)務(wù)邏輯慎颗,例如創(chuàng)建一個實體(數(shù)據(jù)),或者從服務(wù)器中獲取一些數(shù)據(jù)言询。為了實現(xiàn)這些功能俯萎,需要使用服務(wù)、管理器运杭,但是他們并不被認(rèn)為是VIPER架構(gòu)內(nèi)的模塊夫啊,而是外部依賴。
展示器 -- 包含UI層面的業(yè)務(wù)邏輯以及在交互器層面的方法調(diào)用辆憔。
實體 -- 普通的數(shù)據(jù)對象撇眯,不屬于數(shù)據(jù)訪問層次,因為數(shù)據(jù)訪問屬于交互器的職責(zé)虱咧。
路由器 -- 用來連接VIPER的各個模塊熊榛。
基本上,VIPER模塊可以是一個屏幕或者用戶使用應(yīng)用的整個過程--想想認(rèn)證過程腕巡,可以由一屏完成或者需要幾步才能完成玄坦,你的模塊期望是多大的,這取決于你。

當(dāng)我們把VIPER和MV(X)系列作比較時煎楣,我們會在任務(wù)均攤性方面發(fā)現(xiàn)一些不同:

Model 邏輯通過把實體作為最小的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換到交互器中豺总。
Controller/Presenter/ViewModel的UI展示方面的職責(zé)移到了Presenter中,但是并沒有數(shù)據(jù)轉(zhuǎn)換相關(guān)的操作择懂。
VIPER是第一個通過路由器實現(xiàn)明確的地址導(dǎo)航模式喻喳。
“找到一個適合的方法來實現(xiàn)路由對于iOS應(yīng)用是一個挑戰(zhàn),MV(X)系列避開了這個問題困曙”砺祝”

例子中并不包含路由和模塊之間的交互,所以和MV(X)系列部分架構(gòu)一樣不再給出例子慷丽。

import UIKit
 
struct Person { // Entity (usually more complex e.g. NSManagedObject)
    let firstName: String
    let lastName: String
}
 
struct GreetingData { // Transport data structure (not Entity)
    let greeting: String
    let subject: String
}
 
protocol GreetingProvider {
    func provideGreetingData()
}
 
protocol GreetingOutput: class {
    func receiveGreetingData(greetingData: GreetingData)
}
 
class GreetingInteractor : GreetingProvider {
    weak var output: GreetingOutput!
     
    func provideGreetingData() {
        let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer
        let subject = person.firstName + " " + person.lastName
        let greeting = GreetingData(greeting: "Hello", subject: subject)
        self.output.receiveGreetingData(greeting)
    }
}
 
protocol GreetingViewEventHandler {
    func didTapShowGreetingButton()
}
 
protocol GreetingView: class {
    func setGreeting(greeting: String)
}
 
class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {
    weak var view: GreetingView!
    var greetingProvider: GreetingProvider!
     
    func didTapShowGreetingButton() {
        self.greetingProvider.provideGreetingData()
    }
     
    func receiveGreetingData(greetingData: GreetingData) {
        let greeting = greetingData.greeting + " " + greetingData.subject
        self.view.setGreeting(greeting)
    }
}
 
class GreetingViewController : UIViewController, GreetingView {
    var eventHandler: GreetingViewEventHandler!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
     
    func didTapButton(button: UIButton) {
        self.eventHandler.didTapShowGreetingButton()
    }
     
    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
     
    // layout code goes here
}
// Assembling of VIPER module, without Router
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter

讓我們再來評估一下特性:

任務(wù)均攤 -- 毫無疑問绑榴,VIPER是任務(wù)劃分中的佼佼者。
可測試性 -- 不出意外地盈魁,更好的分布性就有更好的可測試性。
易用性 -- 最后你可能已經(jīng)猜到了維護(hù)成本方面的問題窃诉。你必須為很小功能的類寫出大量的接口杨耙。
什么是LEGO

當(dāng)使用VIPER時,你的感覺就像是用樂高積木來搭建一個城堡飘痛,這也是一個表明當(dāng)前存在一些問題的信號珊膜。可能現(xiàn)在就應(yīng)用VIPER架構(gòu)還為時過早宣脉,考慮一些更為簡單的模式可能會更好车柠。一些人會忽略這些問題,大材小用塑猖。假定他們篤信VIPER架構(gòu)會在未來給他們的應(yīng)用帶來一些好處竹祷,雖然現(xiàn)在維護(hù)起來確實是有些不合理。如果你也持這樣的觀點羊苟,我為你推薦 Generamba 這個用來搭建VIPER架構(gòu)的工具塑陵。雖然我個人感覺,使用起來就像加農(nóng)炮的自動瞄準(zhǔn)系統(tǒng)蜡励,而不是簡單的像投石器那樣的簡單的拋擲令花。

總結(jié)

我們了解了集中架構(gòu)模式,希望你已經(jīng)找到了到底是什么在困擾你凉倚。毫無疑問通過閱讀本篇文章兼都,你已經(jīng)了解到其實并沒有完全的銀彈。所以選擇架構(gòu)是一個根據(jù)實際情況具體分析利弊的過程稽寒。

因此扮碧,在同一個應(yīng)用中包含著多種架構(gòu)。比如瓦胎,你開始的時候使用MVC芬萍,然后突然意識到一個頁面在MVC模式下的變得越來越難以維護(hù)尤揣,然后就切換到MVVM架構(gòu),但是僅僅針對這一個頁面柬祠。并沒有必要對哪些MVC模式下運轉(zhuǎn)良好的頁面進(jìn)行重構(gòu)北戏,因為二者是可以并存的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漫蛔,一起剝皮案震驚了整個濱河市嗜愈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莽龟,老刑警劉巖蠕嫁,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異毯盈,居然都是意外死亡剃毒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門搂赋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赘阀,“玉大人,你說我怎么就攤上這事脑奠』” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵宋欺,是天一觀的道長轰豆。 經(jīng)常有香客問我,道長齿诞,這世上最難降的妖魔是什么酸休? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮祷杈,結(jié)果婚禮上雨席,老公的妹妹穿的比我還像新娘。我一直安慰自己吠式,他們只是感情好陡厘,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著特占,像睡著了一般糙置。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上是目,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天谤饭,我揣著相機(jī)與錄音,去河邊找鬼。 笑死揉抵,一個胖子當(dāng)著我的面吹牛亡容,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冤今,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼闺兢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了戏罢?” 一聲冷哼從身側(cè)響起屋谭,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎龟糕,沒想到半個月后桐磁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡讲岁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年我擂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缓艳。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡扶踊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出郎任,到底是詐尸還是另有隱情,我是刑警寧澤备籽,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布舶治,位于F島的核電站,受9級特大地震影響车猬,放射性物質(zhì)發(fā)生泄漏霉猛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一珠闰、第九天 我趴在偏房一處隱蔽的房頂上張望惜浅。 院中可真熱鬧,春花似錦伏嗜、人聲如沸坛悉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裸影。三九已至,卻和暖如春军熏,著一層夾襖步出監(jiān)牢的瞬間轩猩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留均践,地道東北人晤锹。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像彤委,于是被迫代替她去往敵國和親鞭铆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容