日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

GUI应用程序架构的十年变迁:MVC,MVP,MVVM,Unidirectional,Clean

發(fā)布時(shí)間:2025/3/21 c/c++ 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 GUI应用程序架构的十年变迁:MVC,MVP,MVVM,Unidirectional,Clean 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

? ? ?十年前,Martin Fowler撰寫了GUI Architectures一文,至今被奉為經(jīng)典。本文所談的所謂架構(gòu)二字,核心即是對(duì)于富客戶端的代碼組織/職責(zé)劃分。縱覽這十年內(nèi)的架構(gòu)模式變遷,大概可以分為MV*與Unidirectional兩大類,而Clean Architecture則是以嚴(yán)格的層次劃分獨(dú)辟蹊徑。從筆者的認(rèn)知來看,從MVC到MVP的變遷完成了對(duì)于View與Model的解耦合,改進(jìn)了職責(zé)分配與可測(cè)試性。而從MVP到MVVM,添加了View與ViewModel之間的數(shù)據(jù)綁定,使得View完全的無狀態(tài)化。最后,整個(gè)從MV*到Unidirectional的變遷即是采用了消息隊(duì)列式的數(shù)據(jù)流驅(qū)動(dòng)的架構(gòu),并且以Redux為代表的方案將原本MV*中碎片化的狀態(tài)管理變?yōu)榱私y(tǒng)一的狀態(tài)管理,保證了狀態(tài)的有序性與可回溯性。

筆者在撰寫本文的時(shí)候也不可避免的帶了很多自己的觀點(diǎn),在漫長的GUI架構(gòu)模式變遷過程中,很多概念其實(shí)是交錯(cuò)復(fù)雜,典型的譬如MVP與MVVM的區(qū)別,筆者按照自己的理解強(qiáng)行定義了二者的區(qū)分邊界,不可避免的帶著自己的主觀想法。另外,鑒于筆者目前主要進(jìn)行的是Web方面的開發(fā),因此在整體傾向上是支持Unidirectional Architecture并且認(rèn)為集中式的狀態(tài)管理是正確的方向。但是必須要強(qiáng)調(diào),GUI架構(gòu)本身是無法脫離其所依托的平臺(tái),下文筆者也會(huì)淺述由于Android與iOS本身SDK API的特殊性,生搬硬套其他平臺(tái)的架構(gòu)模式也是邯鄲學(xué)步,沐猴而冠。不過總結(jié)而言,它山之石,可以攻玉,本身我們所處的開發(fā)環(huán)境一直在不斷變化,對(duì)于過去的精華自當(dāng)應(yīng)該保留,并且與新的環(huán)境相互印證,觸類旁通。

Introduction

Make everything as simple as possible, but not simpler?—?Albert Einstein

Graphical User Interfaces一直是軟件開發(fā)領(lǐng)域的重要組成部分,從當(dāng)年的MFC,到WinForm/Java Swing,再到WebAPP/Android/iOS引領(lǐng)的智能設(shè)備潮流,以及未來可能的AR/VR,GUI應(yīng)用開發(fā)中所面臨的問題一直在不斷演變,但是從各種具體問題中抽象而出的可以復(fù)用的模式恒久存在。而這些模式也就是所謂應(yīng)用架構(gòu)的核心與基礎(chǔ)。對(duì)于所謂應(yīng)用架構(gòu),空談?wù)`事,不談?wù)`己,筆者相信不僅僅只有自己想把那一團(tuán)糟的代碼給徹底拋棄。往往對(duì)于架構(gòu)的認(rèn)知需要一定的大局觀與格局眼光,每個(gè)有一定經(jīng)驗(yàn)的客戶端程序開發(fā)者,無論是Web、iOS還是Android,都會(huì)有自己熟悉的開發(fā)流程習(xí)慣,但是筆者認(rèn)為架構(gòu)認(rèn)知更多的是道,而非術(shù)。當(dāng)你能夠以一種指導(dǎo)思想在不同的平臺(tái)上能夠進(jìn)行高效地開發(fā)時(shí),你才能真正理解架構(gòu)。這個(gè)有點(diǎn)像張三豐學(xué)武,心中無招,方才達(dá)成。筆者這么說只是為了強(qiáng)調(diào),盡量地可以不拘泥于某個(gè)平臺(tái)的具體實(shí)現(xiàn)去審視GUI應(yīng)用程序架構(gòu)模式,會(huì)讓你有不一樣的體驗(yàn)。譬如下面這個(gè)組裝Android機(jī)器人的圖:

怎么去焊接兩個(gè)組件,屬于具體的術(shù)實(shí)現(xiàn),而應(yīng)該焊接哪兩個(gè)組件就是術(shù),作為合格的架構(gòu)師總不能把腳和頭直接焊接在一起,而忽略中間的連接模塊。對(duì)于軟件開發(fā)中任何一個(gè)方面,我們都希望能夠?qū)ふ业揭粋€(gè)抽象程度適中,能夠在接下來的4,5年內(nèi)正常運(yùn)行與方便維護(hù)擴(kuò)展的開發(fā)模式。引申下筆者在我的編程之路中的論述,目前在GUI架構(gòu)模式中,無論是Android、iOS還是Web,都在經(jīng)歷著從命令式編程到聲明式/響應(yīng)式編程,從Passive Components到Reactive Components,從以元素操作為核心到以數(shù)據(jù)流驅(qū)動(dòng)為核心的變遷(關(guān)于這幾句話的解釋可以參閱下文的Declarative vs. Imperative這一小節(jié))。

Terminology:名詞解釋

正文之前,我們先對(duì)一些概念進(jìn)行闡述:

  • User Events/用戶事件:即是來自于可輸入設(shè)備上的用戶操作產(chǎn)生的數(shù)據(jù),譬如鼠標(biāo)點(diǎn)擊、滾動(dòng)、鍵盤輸入、觸摸等等。

  • User Interface Rendering/用戶界面渲染:View這個(gè)名詞在前后端開發(fā)中都被廣泛使用,為了明晰該詞的含義,我們?cè)谶@里使用用戶渲染這個(gè)概念,來描述View,即是以HTML或者JSX或者XAML等等方式在屏幕上產(chǎn)生的圖形化輸出內(nèi)容。

  • UI Application:允許接收用戶輸入,并且將輸出渲染到屏幕上的應(yīng)用程序,該程序能夠長期運(yùn)行而不只是渲染一次即結(jié)束

Passive Module & Reactive Module

箭頭表示的歸屬權(quán)實(shí)際上也是Passive Programming與Reactive Programming的區(qū)別,譬如我們的系統(tǒng)中有Foo與Bar兩個(gè)模塊,可以把它們當(dāng)做OOP中的兩個(gè)類。如果我們?cè)贔oo與Bar之間建立一個(gè)箭頭,也就意味著Foo能夠影響B(tài)ar中的狀態(tài):

譬如Foo在進(jìn)行一次網(wǎng)絡(luò)請(qǐng)求之后將Bar內(nèi)部的計(jì)數(shù)器加一操作:

// This is inside the Foo modulefunction onNetworkRequest() {// ...Bar.incrementCounter();// ... }

在這里將這種邏輯關(guān)系可以描述為Foo擁有著網(wǎng)絡(luò)請(qǐng)求完成之后將Bar內(nèi)的計(jì)數(shù)器加一這個(gè)關(guān)系的控制權(quán),也就是Foo占有主導(dǎo)性,而Bar相對(duì)而言是Passive被動(dòng)的:

Bar是Passive的,它允許其他模塊改變其內(nèi)部狀態(tài)。而Foo是主動(dòng)地,它需要保證能夠正確地更新Bar的內(nèi)部狀態(tài),Passive模塊并不知道誰會(huì)更新到它。而另一種方案就是類似于控制反轉(zhuǎn),由Bar完成對(duì)于自己內(nèi)部狀態(tài)的更新:

在這種模式下,Bar監(jiān)聽來自于Foo中的事件,并且在某些事件發(fā)生之后進(jìn)行內(nèi)部狀態(tài)更新:

// This is inside the Bar moduleFoo.addOnNetworkRequestListener(() => {self.incrementCounter(); // self is Bar});

此時(shí)Bar就變成了Reactive Module,它負(fù)責(zé)自己的內(nèi)部的狀態(tài)更新以響應(yīng)外部的事件,而Foo并不知道它發(fā)出的事件會(huì)被誰監(jiān)聽。

Declarative vs. Imperative:命令式編程與聲明式編程

three-ds-of-web-development

前端攻略-從路人甲到英雄無敵二:JavaScript 與不斷演化的框架

形象地來描述命令式編程與聲明式編程的區(qū)別,就好像C#/JavaScript與類似于XML或者HTML這樣的標(biāo)記語言之間的區(qū)別。命令式編程關(guān)注于how to do what you want done,即事必躬親,需要安排好每個(gè)要做的細(xì)節(jié)。而聲明式編程關(guān)注于what you want done without worrying about how,即只需要聲明要做的事情而不用將具體的過程再耦合進(jìn)來。對(duì)于開發(fā)者而言,聲明式編程將很多底層的實(shí)現(xiàn)細(xì)節(jié)向開發(fā)者隱藏,而使得開發(fā)者可以專注于具體的業(yè)務(wù)邏輯,同時(shí)也保證了代碼的解耦與單一職責(zé)。譬如在Web開發(fā)中,如果你要基于jQuery將數(shù)據(jù)填充到頁面上,那么大概按照命令式編程的模式你需要這么做:

var options = $("#options"); $.each(result, function() {options.append($("<option />").val(this.id).text(this.name)); });

而以Angular 1聲明式的方式進(jìn)行編寫,那么是如下的標(biāo)記模樣:

<div ng-repeat="item in items" ng-click="select(item)">{{item.name}} </div>

而在iOS和Android開發(fā)中,近年來函數(shù)響應(yīng)式編程(Functional Reactive Programming)也非常流行,參閱筆者關(guān)于響應(yīng)式編程的介紹可以了解,響應(yīng)式編程本身是基于流的方式對(duì)于異步操作的一種編程優(yōu)化,其在整個(gè)應(yīng)用架構(gòu)的角度看更多的是細(xì)節(jié)點(diǎn)的優(yōu)化。以RxSwift為例,通過響應(yīng)式編程可以編寫出非常優(yōu)雅的用戶交互代碼:

let searchResults = searchBar.rx_text.throttle(0.3, scheduler: MainScheduler.instance).distinctUntilChanged().flatMapLatest { query -> Observable<[Repository]> inif query.isEmpty {return Observable.just([])}return searchGitHub(query).catchErrorJustReturn([])}.observeOn(MainScheduler.instance) searchResults.bindTo(tableView.rx_itemsWithCellIdentifier("Cell")) {(index, repository: Repository, cell) incell.textLabel?.text = repository.namecell.detailTextLabel?.text = repository.url}.addDisposableTo(disposeBag)

其直觀的效果大概如下圖所示:

到這里可以看出,無論是從命令式編程與聲明式編程的對(duì)比還是響應(yīng)式編程的使用,我們開發(fā)時(shí)的關(guān)注點(diǎn)都慢慢轉(zhuǎn)向了所謂的數(shù)據(jù)流。便如MVVM,雖然它還是雙向數(shù)據(jù)流,但是其使用的Data-Binding也意味著開發(fā)人員不需要再去以命令地方式尋找元素,而更多地關(guān)注于應(yīng)該給綁定的對(duì)象賦予何值,這也是數(shù)據(jù)流驅(qū)動(dòng)的一個(gè)重要體現(xiàn)。而Unidirectional Architecture采用了類似于Event Source的方式,更是徹底地將組件之間、組件與功能模塊之間的關(guān)聯(lián)交于數(shù)據(jù)流操控。

談到架構(gòu),我們關(guān)心哪些方面?

當(dāng)我們談?wù)撍^客戶端開發(fā)的時(shí)候,我們首先會(huì)想到怎么保證向后兼容、怎么使用本地存儲(chǔ)、怎么調(diào)用遠(yuǎn)程接口、如何有效地利用內(nèi)存/帶寬/CPU等資源,不過最核心的還是怎么繪制界面并且與用戶進(jìn)行交互,關(guān)于這部分詳細(xì)的知識(shí)點(diǎn)綱要推薦參考筆者的我的編程之路——知識(shí)管理與知識(shí)體系這篇文章或者這張知識(shí)點(diǎn)列表思維腦圖。

而當(dāng)我們提綱挈領(lǐng)、高屋建瓴地以一個(gè)較高的抽象的視角來審視總結(jié)這個(gè)知識(shí)點(diǎn)的時(shí)候會(huì)發(fā)現(xiàn),我們希望的好的架構(gòu),便如在引言中所說,即是有好的代碼組織方式/合理的職責(zé)劃分粒度。筆者腦中會(huì)出現(xiàn)如下這樣的一個(gè)層次結(jié)構(gòu),可以看出,最核心的即為View與ViewLogic這兩部分:

實(shí)際上,對(duì)于富客戶端的代碼組織/職責(zé)劃分,從具體的代碼分割的角度,即是功能的模塊化界面的組件化狀態(tài)管理這三個(gè)方面。最終呈獻(xiàn)給用戶的界面,筆者認(rèn)為可以抽象為如下等式:View = f(State,Template)。而ViewLogic中對(duì)于類/模塊之間的依賴關(guān)系,即屬于代碼組織,譬如MVC中的View與Controller之間的從屬關(guān)系。而對(duì)于動(dòng)態(tài)數(shù)據(jù),即所謂應(yīng)用數(shù)據(jù)的管理,屬于狀態(tài)管理這一部分,譬如APP從后來獲取了一系列的數(shù)據(jù),如何將這些數(shù)據(jù)渲染到用戶界面上使得用戶可見,這樣的不同部分之間的協(xié)同關(guān)系、整個(gè)數(shù)據(jù)流的流動(dòng),即屬于狀態(tài)管理。

分久必合,合久必分

實(shí)際上從MVC、MVP到MVVM,一直圍繞的核心問題就是如何分割ViewLogic與View,即如何將負(fù)責(zé)界面展示的代碼與負(fù)責(zé)業(yè)務(wù)邏輯的代碼進(jìn)行分割。所謂分久必合,合久必分,從筆者自我審視的角度,發(fā)現(xiàn)很有趣的一點(diǎn)。Android與iOS中都是從早期的用代碼進(jìn)行組件添加與布局到專門的XML/Nib/StoryBoard文件進(jìn)行布局,Android中的Annotation/DataBinding、iOS中的IBOutlet更加地保證了View與ViewLogic的分割(這一點(diǎn)也是從元素操作到以數(shù)據(jù)流驅(qū)動(dòng)的變遷,我們不需要再去編寫大量的findViewById)。而Web的趨勢(shì)正好有點(diǎn)相反,無論是WebComponent還是ReactiveComponent都是將ViewLogic與View置于一起,特別是JSX的語法將JavaScript與HTML混搭,很像當(dāng)年的PHP/JSP與HTML混搭。這一點(diǎn)也是由筆者在上文提及的Android/iOS本身封裝程度較高的、規(guī)范的API決定的。對(duì)于Android/iOS與Web之間開發(fā)體驗(yàn)的差異,筆者感覺很類似于靜態(tài)類型語言與動(dòng)態(tài)類型語言之間的差異。

功能的模塊化

老實(shí)說在AMD/CMD規(guī)范之前,或者說在ES6的模塊引入與Webpack的模塊打包出來之前,功能的模塊化依賴一直也是個(gè)很頭疼的問題。

SOLID中的接口隔離原則,大量的IOC或者DI工具可以幫我們完成這一點(diǎn),就好像Spring中的@Autowire或者Angular 1中的@Injection,都給筆者很好地代碼體驗(yàn)。

在這里筆者首先要強(qiáng)調(diào)下,從代碼組織的角度來看,項(xiàng)目的構(gòu)建工具與依賴管理工具會(huì)深刻地影響到代碼組織,這一點(diǎn)在功能的模塊化中尤其顯著。譬如筆者對(duì)于Android/Java構(gòu)建工具的使用變遷經(jīng)歷了從Eclipse到Maven再到Gradle,筆者會(huì)將不同功能邏輯的代碼封裝到不同的相對(duì)獨(dú)立的子項(xiàng)目中,這樣就保證了子項(xiàng)目與主項(xiàng)目之間的一定隔離,方便了測(cè)試與代碼維護(hù)。同樣的,在Web開發(fā)中從AMD/CMD規(guī)范到標(biāo)準(zhǔn)的ES6模塊與Webpack編譯打包,也使得代碼能夠按照功能盡可能地解耦分割與避免冗余編碼。而另一方面,依賴管理工具也極大地方便我們使用第三方的代碼與發(fā)布自定義的依賴項(xiàng),譬如Web中的NPM與Bower,iOS中的CocoaPods都是十分優(yōu)秀的依賴發(fā)布與管理工具,使我們不需要去關(guān)心第三方依賴的具體實(shí)現(xiàn)細(xì)節(jié)即能夠透明地引入使用。因此選擇合適的項(xiàng)目構(gòu)建工具與依賴管理工具也是好的GUI架構(gòu)模式的重要因素之一。不過從應(yīng)用程序架構(gòu)的角度看,無論我們使用怎樣的構(gòu)建工具,都可以實(shí)現(xiàn)或者遵循某種架構(gòu)模式,筆者認(rèn)為二者之間也并沒有必然的因果關(guān)系。

界面的組件化

A component is a small piece of the user interface of our application, a view, that can be composed with other components to make more advanced components.

何謂組件?一個(gè)組件即是應(yīng)用中用戶交互界面的部分組成,組件可以通過組合封裝成更高級(jí)的組件。組件可以被放入層次化的結(jié)構(gòu)中,即可以是其他組件的父組件也可以是其他組件的子組件。根據(jù)上述的組件定義,筆者認(rèn)為像Activity或者UIViewController都不能算是組件,而像ListView或者UITableView可以看做典型的組件。

我們強(qiáng)調(diào)的是界面組件的Composable&Reusable,即可組合性與可重用性。當(dāng)我們一開始接觸到Android或者iOS時(shí),因?yàn)楸旧鞸DK的完善度與規(guī)范度較高,我們能夠很多使用封裝程度較高的組件。譬如ListView,無論是Android中的RecycleView還是iOS中的UITableView或者UICollectionView,都為我們提供了。凡事都有雙面性,這種較高程度的封裝與規(guī)范統(tǒng)一的API方便了我們的開發(fā),但是也限制了我們自定義的能力。同樣的,因?yàn)镾DK的限制,真正意義上可復(fù)用/組合的組件也是不多,譬如你不能將兩個(gè)ListView再組合成一個(gè)新的ListView。在React中有所謂的controller-view的概念,即意味著某個(gè)React組件同時(shí)擔(dān)負(fù)起MVC中Controller與View的責(zé)任,也就是JSX這種將負(fù)責(zé)ViewLogic的JavaScript代碼與負(fù)責(zé)模板的HTML混編的方式。

界面的組件化還包括一個(gè)重要的點(diǎn)就是路由,譬如Android中的AndRouter、iOS中的JLRoutes都是集中式路由的解決方案,不過集中式路由在Android或者iOS中并沒有大規(guī)模推廣。iOS中的StoryBoard倒是類似于一種集中式路由的方案,不過更偏向于以UI設(shè)計(jì)為核心。筆者認(rèn)為這一點(diǎn)可能是因?yàn)锳ndroid或者iOS本身所有的代碼都是存放于客戶端本身,而Web中較傳統(tǒng)的多頁應(yīng)用方式還需要用戶跳轉(zhuǎn)頁面重新加載,而后在單頁流行之后即不存在頁面級(jí)別的跳轉(zhuǎn),因此在Web單頁應(yīng)用中集中式路由較為流行而Android、iOS中反而不流行。

無狀態(tài)的組件

無狀態(tài)的組件的構(gòu)建函數(shù)是純函數(shù)(pure function)并且引用透明的(refferentially transparent),在相同輸入的情況下一定會(huì)產(chǎn)生相同的組件輸出,即符合View = f(State,Template)公式。筆者覺得Android中的ListView/RecycleView,或者iOS中的UITableView,也是無狀態(tài)組件的典型。譬如在Android中,可以通過動(dòng)態(tài)設(shè)置Adapter實(shí)例來為RecycleView進(jìn)行源數(shù)據(jù)的設(shè)置,而作為View層以IoC的方式與具體的數(shù)據(jù)邏輯解耦。

組件的可組合性與可重用性往往最大的阻礙就是狀態(tài),一般來說,我們希望能夠重用或者組合的組件都是

Generalization,而狀態(tài)往往是Specification,即領(lǐng)域特定的。同時(shí),狀態(tài)也會(huì)使得代碼的可讀性與可測(cè)試性降低,在有狀態(tài)的組件中,我們并不能通過簡單地閱讀代碼就知道其功能。如果借用函數(shù)式編程的概念,就是因?yàn)楦弊饔玫囊胧沟煤瘮?shù)每次回產(chǎn)生不同的結(jié)果。函數(shù)式編程中存在著所謂Pure Function,即純函數(shù)的概念,函數(shù)的返回值永遠(yuǎn)只受到輸入?yún)?shù)的影響。譬如(x)=>x*2這個(gè)函數(shù),輸入的x值永遠(yuǎn)不會(huì)被改變,并且返回值只是依賴于輸入的參數(shù)。而Web開發(fā)中我們也經(jīng)常會(huì)處于帶有狀態(tài)與副作用的環(huán)境,典型的就是Browser中的DOM,之前在jQuery時(shí)代我們會(huì)經(jīng)常將一些數(shù)據(jù)信息緩存在DOM樹上,也是典型的將狀態(tài)與模板混合的用法。這就導(dǎo)致了我們并不能控制到底應(yīng)該何時(shí)去進(jìn)行重新渲染以及哪些狀態(tài)變更的操作才是必須的,

var Header = component(function (data) {// First argument is h1 metadatareturn h1(null, data.text); });// Render the component to our DOM render(Header({text: 'Hello'}), document.body);// Some time later, we change it, by calling the // component once more. setTimeout(function () {render(Header({text: 'Changed'}), document.body); }, 1000); var hello = Header({ text: 'Hello' }); var bye = Header({ text: 'Good Bye' });

狀態(tài)管理

可變的與不可預(yù)測(cè)的狀態(tài)是軟件開發(fā)中的萬惡之源

  • Web開發(fā)中所謂狀態(tài)淺析:Domain State&UI State

上文提及,我們盡可能地希望組件的無狀態(tài)性,那么整個(gè)應(yīng)用中的狀態(tài)管理應(yīng)該盡量地放置在所謂High-Order Component或者Smart Component中。在React以及Flux的概念流行之后,Stateless Component的概念深入人心,不過其實(shí)對(duì)于MVVM中的View,也是無狀態(tài)的View。通過雙向數(shù)據(jù)綁定將界面上的某個(gè)元素與ViewModel中的變量相關(guān)聯(lián),筆者認(rèn)為很類似于HOC模式中的Container與Component之間的關(guān)聯(lián)。隨著應(yīng)用的界面與功能的擴(kuò)展,狀態(tài)管理會(huì)變得愈發(fā)混亂。這一點(diǎn),無論前后端都有異曲同工之難,筆者在基于Redux思想與RxJava的SpringMVC中Controller的代碼風(fēng)格實(shí)踐一文中對(duì)于服務(wù)端應(yīng)用程序開發(fā)中的狀態(tài)管理有過些許討論。

Features of Good Architectural Pattern:何為好的架構(gòu)模式

Balanced Distribution of Responsibilities:合理的職責(zé)劃分

合理的職責(zé)劃分即是保證系統(tǒng)中的不同組件能夠被分配合理的職責(zé),也就是在復(fù)雜度之間達(dá)成一個(gè)平衡,職責(zé)劃分最權(quán)威的原則就是所謂Single Responsibility Principle,單一職責(zé)原則。

Testability:可測(cè)試性

可測(cè)試性是保證軟件工程質(zhì)量的重要手段之一,也是保證產(chǎn)品可用性的重要途徑。在傳統(tǒng)的GUI程序開發(fā)中,特別是對(duì)于界面的測(cè)試常常設(shè)置于狀態(tài)或者運(yùn)行環(huán)境,并且很多與用戶交互相關(guān)的測(cè)試很難進(jìn)行場(chǎng)景重現(xiàn),或者需要大量的人工操作去模擬真實(shí)環(huán)境。

Ease of Use:易用性

代碼的易用性保證了程序架構(gòu)的簡潔與可維護(hù)性,所謂最好的代碼就是永遠(yuǎn)不需要重寫的代碼,而程序開發(fā)中盡量避免的代碼復(fù)用方法就是復(fù)制粘貼。

Fractal:碎片化,易于封裝與分發(fā)

In fractal architectures, the whole can be naively packaged as a component to be used in some larger application.In non-fractal architectures, the non-repeatable parts are said to be orchestrators over the parts that have hierarchical composition.

  • By André Staltz

所謂的Fractal Architectures,即你的應(yīng)用整體都可以像單個(gè)組件一樣可以方便地進(jìn)行打包然后應(yīng)用到其他項(xiàng)目中。而在Non-Fractal Architectures中,不可以被重復(fù)使用的部分被稱為層次化組合中的Orchestrators。譬如你在Web中編寫了一個(gè)登錄表單,其中的布局、樣式等部分可以被直接復(fù)用,而提交表單這個(gè)操作,因?yàn)榫哂袘?yīng)用特定性,因此需要在不同的應(yīng)用中具有不同的實(shí)現(xiàn)。譬如下面有一個(gè)簡單的表單:

<form action="form_action.asp" method="get"><p>First name: <input type="text" name="fname" /></p><p>Last name: <input type="text" name="lname" /></p><input type="submit" value="Submit" /> </form>

因?yàn)椴煌膽?yīng)用中,form的提交地址可能不一致,那么整個(gè)form組件是不可直接重用的,即Non-Fractal Architectures。而form中的input組件是可以進(jìn)行直接復(fù)用的,如果將input看做一個(gè)單獨(dú)的GUI架構(gòu),即是所謂的Fractal Architectures,form就是所謂的Orchestrators,將可重用的組件編排組合,并且設(shè)置應(yīng)用特定的一些信息。

Reference

Overview

  • Martin Fowler-GUI Architectures

  • Comparison-of-Architecture-presentation-patterns

MV*

  • THE EVOLUTION OF ANDROID ARCHITECTURE

  • the-evolution-of-android-architecture

  • android-architecture

  • ios-architecture-patterns

  • Albert Zuurbier:MVC VS. MVP VS. MVVM

MVC

  • Model-View-Controller (MVC) in iOS: A Modern Approach

  • 為什么我不再使用MVC框架

  • difference-between-mvc-mvp-mvvm-swapneel-salunkhe

MVP

  • presentation-model-and-passive-view-in-mvp-the-android-way

  • Repository that showcases 3 Android app architectures

MVVM

  • approaching-android-with-mvvm

Unidirectional Architecture

  • unidirectional-user-interface-architectures

  • [Facebook: MVC Does Not Scale, Use Flux Instead [Updated]](https://www.infoq.com/news/20...

  • mvvm-mvc-is-dead-is-unidirectional-a-mvvm-mvc-killer

  • flux-vs-mvc-design-patterns

  • jedux:Redux architecture for Android

  • writing-a-todo-app-with-redux-on-android

  • state-streams-and-react

Viper/Clean Architecture

  • Uncle Bob:the-clean-architecture

  • Android Clean Architecture

  • A sample iOS app built using the Clean Swift architecture

  • Introduction to VIPER

MV*:Fragmentary State 碎片化的狀態(tài)與雙向數(shù)據(jù)流

MVC模式將有關(guān)于渲染、控制與數(shù)據(jù)存儲(chǔ)的概念有機(jī)分割,是GUI應(yīng)用架構(gòu)模式的一個(gè)巨大成就。但是,MVC模式在構(gòu)建能夠長期運(yùn)行、維護(hù)、有效擴(kuò)展的應(yīng)用程序時(shí)遇到了極大的問題。MVC模式在一些小型項(xiàng)目或者簡單的界面上仍舊有極大的可用性,但是在現(xiàn)代富客戶端開發(fā)中導(dǎo)致職責(zé)分割不明確、功能模塊重用性、View的組合性較差。作為繼任者M(jìn)VP模式分割了View與Model之間的直接關(guān)聯(lián),MVP模式中也將更多的ViewLogic轉(zhuǎn)移到Presenter中進(jìn)行實(shí)現(xiàn),從而保證了View的可測(cè)試性。而最年輕的MVVM將ViewLogic與View剝離開來,保證了View的無狀態(tài)性、可重用性、可組合性以及可測(cè)試性。總結(jié)而言,MV*模型都包含了以下幾個(gè)方面:

  • Models:負(fù)責(zé)存儲(chǔ)領(lǐng)域/業(yè)務(wù)邏輯相關(guān)的數(shù)據(jù)與構(gòu)建數(shù)據(jù)訪問層,典型的就是譬如Person、PersonDataProvider。

  • Views:負(fù)責(zé)將數(shù)據(jù)渲染展示給用戶,并且響應(yīng)用戶輸入

  • Controller/Presenter/ViewModel:往往作為Model與View之間的中間人出現(xiàn),接收View傳來的用戶事件并且傳遞給Model,同時(shí)利用從Model傳來的最新模型控制更新View

MVC:Monolithic Controller

相信每一個(gè)程序猿都會(huì)宣稱自己掌握MVC,這個(gè)概念淺顯易懂,并且貫穿了從GUI應(yīng)用到服務(wù)端應(yīng)用程序。MVC的概念源自Gamma, Helm, Johnson 以及Vlissidis這四人幫在討論設(shè)計(jì)模式中的Observer模式時(shí)的想法,不過在那本經(jīng)典的設(shè)計(jì)模式中并沒有顯式地提出這個(gè)概念。我們通常認(rèn)為的MVC名詞的正式提出是在1979年5月Trygve Reenskaug發(fā)表的Thing-Model-View-Editor這篇論文,這篇論文雖然并沒有提及Controller,但是Editor已經(jīng)是一個(gè)很接近的概念。大概7個(gè)月之后,Trygve Reenskaug在他的文章Models-Views-Controllers中正式提出了MVC這個(gè)三元組。上面兩篇論文中對(duì)于Model的定義都非常清晰,Model代表著an abstraction in the form of data in a computing system.,即為計(jì)算系統(tǒng)中數(shù)據(jù)的抽象表述,而View代表著capable of showing one or more pictorial representations of the Model on screen and on hardcopy.,即能夠?qū)⒛P椭械臄?shù)據(jù)以某種方式表現(xiàn)在屏幕上的組件。而Editor被定義為某個(gè)用戶與多個(gè)View之間的交互接口,在后一篇文章中Controller則被定義為了a special controller ... that permits the user to modify the information that is presented by the view.,即主要負(fù)責(zé)對(duì)模型進(jìn)行修改并且最終呈現(xiàn)在界面上。從我的個(gè)人理解來看,Controller負(fù)責(zé)控制整個(gè)界面,而Editor只負(fù)責(zé)界面中的某個(gè)部分。Controller協(xié)調(diào)菜單、面板以及像鼠標(biāo)點(diǎn)擊、移動(dòng)、手勢(shì)等等很多的不同功能的模塊,而Editor更多的只是負(fù)責(zé)某個(gè)特定的任務(wù)。后來,Martin Fowler在2003開始編寫的著作Patterns of Enterprise Application Architecture中重申了MVC的意義:Model View Controller (MVC) is one of the most quoted (and most misquoted) patterns around.,將Controller的功能正式定義為:響應(yīng)用戶操作,控制模型進(jìn)行相應(yīng)更新,并且操作頁面進(jìn)行合適的重渲染。這是非常經(jīng)典、狹義的MVC定義,后來在iOS以及其他很多領(lǐng)域?qū)嶋H上運(yùn)用的MVC都已經(jīng)被擴(kuò)展或者賦予了新的功能,不過筆者為了區(qū)分架構(gòu)演化之間的區(qū)別,在本文中僅會(huì)以這種最樸素的定義方式來描述MVC。

根據(jù)上述定義,我們可以看到MVC模式中典型的用戶場(chǎng)景為:

  • 用戶交互輸入了某些內(nèi)容

  • Controller將用戶輸入轉(zhuǎn)化為Model所需要進(jìn)行的更改

  • Model中的更改結(jié)束之后,Controller通知View進(jìn)行更新以表現(xiàn)出當(dāng)前Model的狀態(tài)

根據(jù)上述流程,我們可知經(jīng)典的MVC模式的特性為:

  • View、Controller、Model中皆有ViewLogic的部分實(shí)現(xiàn)

  • Controller負(fù)責(zé)控制View與Model,需要了解View與Model的細(xì)節(jié)。

  • View需要了解Controller與Model的細(xì)節(jié),需要在偵測(cè)用戶行為之后調(diào)用Controller,并且在收到通知后調(diào)用Model以獲取最新數(shù)據(jù)

  • Model并不需要了解Controller與View的細(xì)節(jié),相對(duì)獨(dú)立的模塊

Observer Pattern:自帶觀察者模式的MVC

上文中也已提及,MVC濫觴于Observer模式,經(jīng)典的MVC模式也可以與Observer模式相結(jié)合,其典型的用戶流程為:

  • 用戶交互輸入了某些內(nèi)容

  • Controller將用戶輸入轉(zhuǎn)化為Model所需要進(jìn)行的更改

  • View作為Observer會(huì)監(jiān)聽Model中的任意更新,一旦有更新事件發(fā)出,View會(huì)自動(dòng)觸發(fā)更新以展示最新的Model狀態(tài)

可知其與經(jīng)典的MVC模式區(qū)別在于不需要Controller通知View進(jìn)行更新,而是由Model主動(dòng)調(diào)用View進(jìn)行更新。這種改變提升了整體效率,簡化了Controller的功能,不過也導(dǎo)致了View與Model之間的緊耦合。

MVP:Decoupling View and Model 將視圖與模型解耦, View<->Presenter

維基百科將MVP稱為MVC的一個(gè)推導(dǎo)擴(kuò)展,觀其淵源而知其所以然。對(duì)于MVP概念的定義,Microsoft較為明晰,而Martin Fowler的定義最為廣泛接受。MVP模式在WinForm系列以Visual-XXX命名的編程語言與Java Swing等系列應(yīng)用中最早流傳開來,不過后來ASP.NET以及JFaces也廣泛地使用了該模式。在MVP中用戶不再與Presenter進(jìn)行直接交互,而是由View完全接管了用戶交互,譬如窗口上的每個(gè)控件都知道如何響應(yīng)用戶輸入并且合適地渲染來自于Model的數(shù)據(jù)。而所有的事件會(huì)被傳輸給Presenter,Presenter在這里就是View與Model之間的中間人,負(fù)責(zé)控制Model進(jìn)行修改以及將最新的Model狀態(tài)傳遞給View。這里描述的就是典型的所謂Passive View版本的MVP,其典型的用戶場(chǎng)景為:

  • 用戶交互輸入了某些內(nèi)容

  • View將用戶輸入轉(zhuǎn)化為發(fā)送給Presenter

  • Presenter控制Model接收需要改變的點(diǎn)

  • Model將更新之后的值返回給Presenter

  • Presenter將更新之后的模型返回給View

根據(jù)上述流程,我們可知Passive View版本的MVP模式的特性為:

  • View、Presenter、Model中皆有ViewLogic的部分實(shí)現(xiàn)

  • Presenter負(fù)責(zé)連接View與Model,需要了解View與Model的細(xì)節(jié)。

  • View需要了解Presenter的細(xì)節(jié),將用戶輸入轉(zhuǎn)化為事件傳遞給Presenter

  • Model需要了解Presenter的細(xì)節(jié),在完成更新之后將最新的模型傳遞給Presenter

  • View與Model之間相互解耦合

Supervising Controller MVP

簡化Presenter的部分功能,使得Presenter只起到需要復(fù)雜控制或者調(diào)解的操作,而簡單的Model展示轉(zhuǎn)化直接由View與Model進(jìn)行交互:

MVVM:Data Binding & Stateless View 數(shù)據(jù)綁定與無狀態(tài)的View,View<->ViewModels

Model View View-Model模型是MV*家族中最年輕的一位,也是由Microsoft提出,并經(jīng)由Martin Fowler布道傳播。MVVM源于Martin Fowler的Presentation Model,Presentation Model的核心在于接管了View所有的行為響應(yīng),View的所有響應(yīng)與狀態(tài)都定義在了Presentation Model中。也就是說,View不會(huì)包含任意的狀態(tài)。舉個(gè)典型的使用場(chǎng)景,當(dāng)用戶點(diǎn)擊某個(gè)按鈕之后,狀態(tài)信息是從Presentation Model傳遞給Model,而不是從View傳遞給Presentation Model。任何控制組件間的邏輯操作,即上文所述的ViewLogic,都應(yīng)該放置在Presentation Model中進(jìn)行處理,而不是在View層,這一點(diǎn)也是MVP模式與Presentation Model最大的區(qū)別。

MVVM模式進(jìn)一步深化了Presentation Model的思想,利用Data Binding等技術(shù)保證了View中不會(huì)存儲(chǔ)任何的狀態(tài)或者邏輯操作。在WPF中,UI主要是利用XAML或者XML創(chuàng)建,而這些標(biāo)記類型的語言是無法存儲(chǔ)任何狀態(tài)的,就像HTML一樣(因此JSX語法其實(shí)是將View又有狀態(tài)化了),只是允許UI與某個(gè)ViewModel中的類建立映射關(guān)系。渲染引擎根據(jù)XAML中的聲明以及來自于ViewModel的數(shù)據(jù)最終生成呈現(xiàn)的頁面。因?yàn)閿?shù)據(jù)綁定的特性,有時(shí)候MVVM也會(huì)被稱作MVB:Model View Binder。總結(jié)一下,MVVM利用數(shù)據(jù)綁定徹底完成了從命令式編程到聲明式編程的轉(zhuǎn)化,使得View逐步無狀態(tài)化。一個(gè)典型的MVVM的使用場(chǎng)景為:

  • 用戶交互輸入

  • View將數(shù)據(jù)直接傳送給ViewModel,ViewModel保存這些狀態(tài)數(shù)據(jù)

  • 在有需要的情況下,ViewModel會(huì)將數(shù)據(jù)傳送給Model

  • Model在更新完成之后通知ViewModel

  • ViewModel從Model中獲取最新的模型,并且更新自己的數(shù)據(jù)狀態(tài)

  • View根據(jù)最新的ViewModel的數(shù)據(jù)進(jìn)行重新渲染

根據(jù)上述流程,我們可知MVVM模式的特性為:

  • ViewModel、Model中存在ViewLogic實(shí)現(xiàn),View則不保存任何狀態(tài)信息

  • View不需要了解ViewModel的實(shí)現(xiàn)細(xì)節(jié),但是會(huì)聲明自己所需要的數(shù)據(jù)類型,并且能夠知道如何重新渲染

  • ViewModel不需要了解View的實(shí)現(xiàn)細(xì)節(jié)(非命令式編程),但是需要根據(jù)View聲明的數(shù)據(jù)類型傳入對(duì)應(yīng)的數(shù)據(jù)。ViewModel需要了解Model的實(shí)現(xiàn)細(xì)節(jié)。

  • Model不需要了解View的實(shí)現(xiàn)細(xì)節(jié),需要了解ViewModel的實(shí)現(xiàn)細(xì)節(jié)

MV* in iOS

MVC

Cocoa MVC中往往會(huì)將大量的邏輯代碼放入ViewController中,這就導(dǎo)致了所謂的Massive ViewController,而且很多的邏輯操作都嵌入到了View的生命周期中,很難剝離開來。或許你可以將一些業(yè)務(wù)邏輯或者數(shù)據(jù)轉(zhuǎn)換之類的事情放到Model中完成,不過對(duì)于View而言絕大部分時(shí)間僅起到發(fā)送Action給Controller的作用。ViewController逐漸變成了幾乎所有其他組件的Delegate與DataSource,還經(jīng)常會(huì)負(fù)責(zé)派發(fā)或者取消網(wǎng)絡(luò)請(qǐng)求等等職責(zé)。你的代碼大概是這樣的:

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

上面這種寫法直接將View于Model關(guān)聯(lián)起來,其實(shí)算是打破了Cocoa MVC的規(guī)范的,不過這樣也是能夠減少些Controller中的中轉(zhuǎn)代碼呢。這樣一個(gè)架構(gòu)模式在進(jìn)行單元測(cè)試的時(shí)候就顯得麻煩了,因?yàn)槟愕腣iewController與View緊密關(guān)聯(lián),使得其很難去進(jìn)行測(cè)試,因?yàn)槟惚仨殲槊恳粋€(gè)View創(chuàng)建Mock對(duì)象并且管理其生命周期。另外因?yàn)檎麄€(gè)代碼都混雜在一起,即破壞了職責(zé)分離原則,導(dǎo)致了系統(tǒng)的可變性與可維護(hù)性也很差。經(jīng)典的MVC的示例程序如下:

import UIKitstruct Person { // Modellet firstName: Stringlet lastName: String}class GreetingViewController : UIViewController { // View + Controllervar 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.lastNameself.greetingLabel.text = greeting}// layout code goes here}// Assembling of MVClet model = Person(firstName: "David", lastName: "Blaine")let view = GreetingViewController()view.person = model;

上面這種代碼一看就很難測(cè)試,我們可以將生成greeting的代碼移到GreetingModel這個(gè)單獨(dú)的類中,從而進(jìn)行單獨(dú)的測(cè)試。不過我們還是很難去在GreetingViewController中測(cè)試顯示邏輯而不調(diào)用UIView相關(guān)的譬如viewDidLoad、didTapButton等等較為費(fèi)時(shí)的操作。再按照我們上文提及的優(yōu)秀的架構(gòu)的幾個(gè)方面來看:

  • Distribution:View與Model是分割開來了,不過View與Controller是緊耦合的

  • Testability:因?yàn)檩^差的職責(zé)分割導(dǎo)致貌似只有Model部分方便測(cè)試

  • 易用性:因?yàn)槌绦虮容^直觀,可能容易理解。

MVP

Cocoa中MVP模式是將ViewController當(dāng)做純粹的View進(jìn)行處理,而將很多的ViewLogic與模型操作移動(dòng)到Presenter中進(jìn)行,代碼如下:

import UIKitstruct Person { // Modellet firstName: Stringlet lastName: String}protocol GreetingView: class {func setGreeting(greeting: String)}protocol GreetingViewPresenter {init(view: GreetingView, person: Person)func showGreeting()}class GreetingPresenter : GreetingViewPresenter {unowned let view: GreetingViewlet person: Personrequired init(view: GreetingView, person: Person) {self.view = viewself.person = person}func showGreeting() {let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastNameself.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 MVPlet model = Person(firstName: "David", lastName: "Blaine")let view = GreetingViewController()let presenter = GreetingPresenter(view: view, person: model)view.presenter = presenter
  • Distribution:主要的業(yè)務(wù)邏輯分割在了Presenter與Model中,View相對(duì)呆板一點(diǎn)

  • Testability:較為方便地測(cè)試

  • 易用性:代碼職責(zé)分割的更為明顯,不過不像MVC那樣直觀易懂了

MVVM

import UIKitstruct Person { // Modellet firstName: Stringlet lastName: String}protocol GreetingViewModelProtocol: class {var greeting: String? { get }var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did changeinit(person: Person)func showGreeting()}class GreetingViewModel : GreetingViewModelProtocol {let person: Personvar 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 inself.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 MVVMlet model = Person(firstName: "David", lastName: "Blaine")let viewModel = GreetingViewModel(person: model)let view = GreetingViewController()view.viewModel = viewModel
  • Distribution:在Cocoa MVVM中,View相對(duì)于MVP中的View擔(dān)負(fù)了更多的功能,譬如需要構(gòu)建數(shù)據(jù)綁定等等

  • Testability:ViewModel擁有View中的所有數(shù)據(jù)結(jié)構(gòu),因此很容易就可以進(jìn)行測(cè)試

  • 易用性:相對(duì)而言有很多的冗余代碼

MV* in Android

此部分完整代碼在這里,筆者在這里節(jié)選出部分代碼方便對(duì)照演示。Android中的Activity的功能很類似于iOS中的UIViewController,都可以看做MVC中的Controller。在2010年左右經(jīng)典的Android程序大概是這樣的:

TextView mCounterText;Button mCounterIncrementButton;int mClicks = 0;public void onCreate(Bundle b) {super.onCreate(b);mCounterText = (TextView) findViewById(R.id.tv_clicks);mCounterIncrementButton = (Button) findViewById(R.id.btn_increment);mCounterIncrementButton.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {mClicks++;mCounterText.setText(""+mClicks);}});}

后來2013年左右出現(xiàn)了ButterKnife這樣的基于注解的控件綁定框架,此時(shí)的代碼看上去是這樣的:

@Bind(R.id.tv_clicks) mCounterText;@OnClick(R.id.btn_increment)public void onSubmitClicked(View v) {mClicks++;mCounterText.setText("" + mClicks);}

后來Google官方也推出了數(shù)據(jù)綁定的框架,從此MVVM模式在Android中也愈發(fā)流行:

<layout xmlns:android="http://schemas.android.com/apk/res/android"><data><variable name="counter" type="com.example.Counter"/><variable name="counter" type="com.example.ClickHandler"/></data><LinearLayoutandroid:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{counter.value}"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{handlers.clickHandle}"/></LinearLayout></layout>

后來Anvil這樣的受React啟發(fā)的組件式框架以及Jedux這樣借鑒了Redux全局狀態(tài)管理的框架也將Unidirectional 架構(gòu)引入了Android開發(fā)的世界。

MVC

  • 聲明View中的組件對(duì)象或者M(jìn)odel對(duì)象

private Subscription subscription;private RecyclerView reposRecycleView;private Toolbar toolbar;private EditText editTextUsername;private ProgressBar progressBar;private TextView infoTextView;private ImageButton searchButton;
  • 將組件與Activity中對(duì)象綁定,并且聲明用戶響應(yīng)處理函數(shù)

super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);progressBar = (ProgressBar) findViewById(R.id.progress);infoTextView = (TextView) findViewById(R.id.text_info);//Set up ToolBartoolbar = (Toolbar) findViewById(R.id.toolbar);setSupportActionBar(toolbar);//Set up RecyclerViewreposRecycleView = (RecyclerView) findViewById(R.id.repos_recycler_view);setupRecyclerView(reposRecycleView);// Set up search buttonsearchButton = (ImageButton) findViewById(R.id.button_search);searchButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {loadGithubRepos(editTextUsername.getText().toString());}});//Set up username EditTexteditTextUsername = (EditText) findViewById(R.id.edit_text_username);editTextUsername.addTextChangedListener(mHideShowButtonTextWatcher);editTextUsername.setOnEditorActionListener(new TextView.OnEditorActionListener() {@Overridepublic boolean onEditorAction(TextView v, int actionId, KeyEvent event) {if (actionId == EditorInfo.IME_ACTION_SEARCH) {String username = editTextUsername.getText().toString();if (username.length() > 0) loadGithubRepos(username);return true;}return false;}});
  • 用戶輸入之后的更新流程

progressBar.setVisibility(View.VISIBLE);reposRecycleView.setVisibility(View.GONE);infoTextView.setVisibility(View.GONE);ArchiApplication application = ArchiApplication.get(this);GithubService githubService = application.getGithubService();subscription = githubService.publicRepositories(username).observeOn(AndroidSchedulers.mainThread()).subscribeOn(application.defaultSubscribeScheduler()).subscribe(new Subscriber<List<Repository>>() {@Overridepublic void onCompleted() {progressBar.setVisibility(View.GONE);if (reposRecycleView.getAdapter().getItemCount() > 0) {reposRecycleView.requestFocus();hideSoftKeyboard();reposRecycleView.setVisibility(View.VISIBLE);} else {infoTextView.setText(R.string.text_empty_repos);infoTextView.setVisibility(View.VISIBLE);}}@Overridepublic void onError(Throwable error) {Log.e(TAG, "Error loading GitHub repos ", error);progressBar.setVisibility(View.GONE);if (error instanceof HttpException&& ((HttpException) error).code() == 404) {infoTextView.setText(R.string.error_username_not_found);} else {infoTextView.setText(R.string.error_loading_repos);}infoTextView.setVisibility(View.VISIBLE);}@Overridepublic void onNext(List<Repository> repositories) {Log.i(TAG, "Repos loaded " + repositories);RepositoryAdapter adapter =(RepositoryAdapter) reposRecycleView.getAdapter();adapter.setRepositories(repositories);adapter.notifyDataSetChanged();}});

MVP

  • 將Presenter與View綁定,并且將用戶響應(yīng)事件綁定到Presenter中

//Set up presenterpresenter = new MainPresenter();presenter.attachView(this);...// Set up search buttonsearchButton = (ImageButton) findViewById(R.id.button_search);searchButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {presenter.loadRepositories(editTextUsername.getText().toString());}});
  • Presenter中調(diào)用Model更新數(shù)據(jù),并且調(diào)用View中進(jìn)行重新渲染

public void loadRepositories(String usernameEntered) {String username = usernameEntered.trim();if (username.isEmpty()) return;mainMvpView.showProgressIndicator();if (subscription != null) subscription.unsubscribe();ArchiApplication application = ArchiApplication.get(mainMvpView.getContext());GithubService githubService = application.getGithubService();subscription = githubService.publicRepositories(username).observeOn(AndroidSchedulers.mainThread()).subscribeOn(application.defaultSubscribeScheduler()).subscribe(new Subscriber<List<Repository>>() {@Overridepublic void onCompleted() {Log.i(TAG, "Repos loaded " + repositories);if (!repositories.isEmpty()) {mainMvpView.showRepositories(repositories);} else {mainMvpView.showMessage(R.string.text_empty_repos);}}@Overridepublic void onError(Throwable error) {Log.e(TAG, "Error loading GitHub repos ", error);if (isHttp404(error)) {mainMvpView.showMessage(R.string.error_username_not_found);} else {mainMvpView.showMessage(R.string.error_loading_repos);}}@Overridepublic void onNext(List<Repository> repositories) {MainPresenter.this.repositories = repositories;}});}

MVVM

  • XML中聲明數(shù)據(jù)綁定

<data><variablename="viewModel"type="uk.ivanc.archimvvm.viewmodel.MainViewModel"/></data>...<EditTextandroid:id="@+id/edit_text_username"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_toLeftOf="@id/button_search"android:hint="@string/hit_username"android:imeOptions="actionSearch"android:inputType="text"android:onEditorAction="@{viewModel.onSearchAction}"android:textColor="@color/white"android:theme="@style/LightEditText"app:addTextChangedListener="@{viewModel.usernameEditTextWatcher}"/>
  • View中綁定ViewModel

super.onCreate(savedInstanceState);binding = DataBindingUtil.setContentView(this, R.layout.main_activity);mainViewModel = new MainViewModel(this, this);binding.setViewModel(mainViewModel);setSupportActionBar(binding.toolbar);setupRecyclerView(binding.reposRecyclerView);
  • ViewModel中進(jìn)行數(shù)據(jù)操作

public boolean onSearchAction(TextView view, int actionId, KeyEvent event) {if (actionId == EditorInfo.IME_ACTION_SEARCH) {String username = view.getText().toString();if (username.length() > 0) loadGithubRepos(username);return true;}return false;}public void onClickSearch(View view) {loadGithubRepos(editTextUsernameValue);}public TextWatcher getUsernameEditTextWatcher() {return new TextWatcher() {@Overridepublic void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {}@Overridepublic void onTextChanged(CharSequence charSequence, int start, int before, int count) {editTextUsernameValue = charSequence.toString();searchButtonVisibility.set(charSequence.length() > 0 ? View.VISIBLE : View.GONE);}@Overridepublic void afterTextChanged(Editable editable) {}};}

Unidirectional User Interface Architecture:單向數(shù)據(jù)流

Unidirectional User Interface Architecture架構(gòu)的概念源于后端常見的CROS/Event Sourcing模式,其核心思想即是將應(yīng)用狀態(tài)被統(tǒng)一存放在一個(gè)或多個(gè)的Store中,并且所有的數(shù)據(jù)更新都是通過可觀測(cè)的Actions觸發(fā),而所有的View都是基于Store中的狀態(tài)渲染而來。該架構(gòu)的最大優(yōu)勢(shì)在于整個(gè)應(yīng)用中的數(shù)據(jù)流以單向流動(dòng)的方式從而使得有用更好地可預(yù)測(cè)性與可控性,這樣可以保證你的應(yīng)用各個(gè)模塊之間的松耦合性。與MVVM模式相比,其解決了以下兩個(gè)問題:

  • 避免了數(shù)據(jù)在多個(gè)ViewModel中的冗余與不一致問題

  • 分割了ViewModel的職責(zé),使得ViewModel變得更加Clean

Why not Bidirectional(Two-way DataBinding)?

This means that one change (a user input or API response) can affect the state of an application in many places in the code — for example, two-way data binding. That can be hard to maintain and debug.

  • easier-reasoning-with-unidirectional-dataflow-and-immutable-data

Facebook強(qiáng)調(diào),雙向數(shù)據(jù)綁定極不利于代碼的擴(kuò)展與維護(hù)。

從具體的代碼實(shí)現(xiàn)角度來看,雙向數(shù)據(jù)綁定會(huì)導(dǎo)致更改的不可預(yù)期性(UnPredictable),就好像Angular利用Dirty Checking來進(jìn)行是否需要重新渲染的檢測(cè),這導(dǎo)致了應(yīng)用的緩慢,簡直就是來砸場(chǎng)子的。而在采用了單向數(shù)據(jù)流之后,整個(gè)應(yīng)用狀態(tài)會(huì)變得可預(yù)測(cè)(Predictable),也能很好地了解當(dāng)狀態(tài)發(fā)生變化時(shí)到底會(huì)有多少的組件發(fā)生變化。另一方面,相對(duì)集中地狀態(tài)管理,也有助于你不同的組件之間進(jìn)行信息交互或者狀態(tài)共享,特別是像Redux這種強(qiáng)調(diào)Single Store與SIngle State Tree的狀態(tài)管理模式,能夠保證以統(tǒng)一的方式對(duì)于應(yīng)用的狀態(tài)進(jìn)行修改,并且Immutable的概念引入使得狀態(tài)變得可回溯。

譬如Facebook在Flux Overview中舉的例子,當(dāng)我們希望在一個(gè)界面上同時(shí)展示未讀信息列表與未讀信息的總數(shù)目的時(shí)候,對(duì)于MV*就有點(diǎn)惡心了,特別是當(dāng)這兩個(gè)組件不在同一個(gè)ViewModel/Controller中的時(shí)候。一旦我們將某個(gè)未讀信息標(biāo)識(shí)為已讀,會(huì)引起控制已讀信息、未讀信息、未讀信息總數(shù)目等等一系列模型的更新。特別是很多時(shí)候?yàn)榱朔奖阄覀兛赡茉诿總€(gè)ViewModel/Controller都會(huì)設(shè)置一個(gè)數(shù)據(jù)副本,這會(huì)導(dǎo)致依賴連鎖更新,最終導(dǎo)致不可預(yù)測(cè)的結(jié)果與性能損耗。而在Flux中這種依賴是反轉(zhuǎn)的,Store接收到更新的Action請(qǐng)求之后對(duì)數(shù)據(jù)進(jìn)行統(tǒng)一的更新并且通知各個(gè)View,而不是依賴于各個(gè)獨(dú)立的ViewModel/Controller所謂的一致性更新。從職責(zé)劃分的角度來看,除了Store之外的任何模塊其實(shí)都不知道應(yīng)該如何處理數(shù)據(jù),這就保證了合理的職責(zé)分割。這種模式下,當(dāng)我們創(chuàng)建新項(xiàng)目時(shí),項(xiàng)目復(fù)雜度的增長瓶頸也就會(huì)更高,不同于傳統(tǒng)的View與ViewLogic之間的綁定,控制流被獨(dú)立處理,當(dāng)我們添加新的特性,新的數(shù)據(jù),新的界面,新的邏輯處理模塊時(shí),并不會(huì)導(dǎo)致原有模塊的復(fù)雜度增加,從而使得整個(gè)邏輯更加清晰可控。

這里還需要提及一下,很多人應(yīng)該是從React開始認(rèn)知到單向數(shù)據(jù)流這種架構(gòu)模式的,而當(dāng)時(shí)Angular 1的緩慢與性能之差令人發(fā)指,但是譬如Vue與Angular 2的性能就非常優(yōu)秀。借用Vue.js官方的說法,

The virtual-DOM approach provides a functional way to describe your view at any point of time, which is really nice. Because it doesn’t use observables and re-renders the entire app on every update, the view is by definition guaranteed to be in sync with the data. It also opens up possibilities to isomorphic JavaScript applications.

Instead of a Virtual DOM, Vue.js uses the actual DOM as the template and keeps references to actual nodes for data bindings. This limits Vue.js to environments where DOM is present. However, contrary to the common misconception that Virtual-DOM makes React faster than anything else, Vue.js actually out-performs React when it comes to hot updates, and requires almost no hand-tuned optimization. With React, you need to implementshouldComponentUpdate everywhere and use immutable data structures to achieve fully optimized re-renders.

總而言之,筆者認(rèn)為雙向數(shù)據(jù)流與單向數(shù)據(jù)流相比,性能上孰優(yōu)孰劣尚無定論,最大的區(qū)別在于單向數(shù)據(jù)流與雙向數(shù)據(jù)流相比有更好地可控性,這一點(diǎn)在上文提及的函數(shù)響應(yīng)式編程中也有體現(xiàn)。若論快速開發(fā),筆者感覺雙向數(shù)據(jù)綁定略勝一籌,畢竟這種View與ViewModel/ViewLogic之間的直接綁定直觀便捷。而如果是注重于全局的狀態(tài)管理,希望維護(hù)耦合程度較低、可測(cè)試性/可擴(kuò)展性較高的代碼,那么還是單向數(shù)據(jù)流,即Unidirectional Architecture較為合適。一家之言,歡迎討論。

Flux:數(shù)據(jù)流驅(qū)動(dòng)的頁面

Flux不能算是絕對(duì)的先行者,但是在Unidirectional Architecture中卻是最富盛名的一個(gè),也是很多人接觸到的第一個(gè)Unidirectional Architecture。Flux主要由以下幾個(gè)部分構(gòu)成:

  • Stores:存放業(yè)務(wù)數(shù)據(jù)和應(yīng)用狀態(tài),一個(gè)Flux中可能存在多個(gè)Stores

  • View:層次化組合的React組件

  • Actions:用戶輸入之后觸發(fā)View發(fā)出的事件

  • Dispatcher:負(fù)責(zé)分發(fā)Actions

根據(jù)上述流程,我們可知Flux模式的特性為:

  • Dispatcher:Event Bus中設(shè)置有一個(gè)單例的Dispatcher,很多Flux的變種都移除了Dispatcher依賴。

  • 只有View使用可組合的組件:在Flux中只有React的組件可以進(jìn)行層次化組合,而Stores與Actions都不可以進(jìn)行層次化組合。React組件與Flux一般是松耦合的,因此Flux并不是Fractal,Dispatcher與Stores可以被看做Orchestrator。

  • 用戶事件響應(yīng)在渲染時(shí)聲明:在React的render()函數(shù)中,即負(fù)責(zé)響應(yīng)用戶交互,也負(fù)責(zé)注冊(cè)用戶事件的處理器

下面我們來看一個(gè)具體的代碼對(duì)比,首先是以經(jīng)典的Cocoa風(fēng)格編寫一個(gè)簡單的計(jì)數(shù)器按鈕:

class ModelCounterconstructor: (@value=1) ->increaseValue: (delta) =>@value += deltaclass ControllerCounterconstructor: (opts) ->@model_counter = opts.model_counter@observers = []getValue: => @model_counter.valueincreaseValue: (delta) =>@model_counter.increaseValue(delta)@notifyObservers()notifyObservers: =>obj.notify(this) for obj in @observersregisterObserver: (observer) =>@observers.push(observer)class ViewCounterButtonconstructor: (opts) ->@controller_counter = opts.controller_counter@button_class = opts.button_class or 'button_counter'@controller_counter.registerObserver(this)render: =>elm = $("<button class=\"#{@button_class}\">#{@controller_counter.getValue()}</button>")elm.click =>@controller_counter.increaseValue(1)return elmnotify: =>$("button.#{@button_class}").replaceWith(=> @render())

上述代碼邏輯用上文提及的MVC模式圖演示就是:

而如果用Flux模式實(shí)現(xiàn),會(huì)是下面這個(gè)樣子:

# Storeclass CounterStore extends EventEmitterconstructor: ->@count = 0@dispatchToken = @registerToDispatcher()increaseValue: (delta) ->@count += 1getCount: ->return @countregisterToDispatcher: ->CounterDispatcher.register((payload) =>switch payload.typewhen ActionTypes.INCREASE_COUNT@increaseValue(payload.delta))# Actionclass CounterActions@increaseCount: (delta) ->CounterDispatcher.handleViewAction({'type': ActionTypes.INCREASE_COUNT'delta': delta})# ViewCounterButton = React.createClass(getInitialState: ->return {'count': 0}_onChange: ->@setState({count: CounterStore.getCount()})componentDidMount: ->CounterStore.addListener('CHANGE', @_onChange)componentWillUnmount: ->CounterStore.removeListener('CHANGE', @_onChange)render: ->return React.DOM.button({'className': @prop.class}, @state.value))

其數(shù)據(jù)流圖為:

Redux:集中式的狀態(tài)管理

Redux是Flux的所有變種中最為出色的一個(gè),并且也是當(dāng)前Web領(lǐng)域主流的狀態(tài)管理工具,其獨(dú)創(chuàng)的理念與功能深刻影響了GUI應(yīng)用程序架構(gòu)中的狀態(tài)管理的思想。Redux將Flux中單例的Dispatcher替換為了單例的Store,即也是其最大的特性,集中式的狀態(tài)管理。并且Store的定義也不是從零開始單獨(dú)定義,而是基于多個(gè)Reducer的組合,可以把Reducer看做Store Factory。Redux的重要組成部分包括:

  • Singleton Store:管理應(yīng)用中的狀態(tài),并且提供了一個(gè)dispatch(action)函數(shù)。

  • Provider:用于監(jiān)聽Store的變化并且連接像React、Angular這樣的UI框架

  • Actions:基于用戶輸入創(chuàng)建的分發(fā)給Reducer的事件

  • Reducers:用于響應(yīng)Actions并且更新全局狀態(tài)樹的純函數(shù)

根據(jù)上述流程,我們可知Redux模式的特性為:

  • 以工廠模式組裝Stores:Redux允許我以createStore()函數(shù)加上一系列組合好的Reducer函數(shù)來創(chuàng)建Store實(shí)例,還有另一個(gè)applyMiddleware()函數(shù)可以允許在dispatch()函數(shù)執(zhí)行前后鏈?zhǔn)秸{(diào)用一系列中間件。

  • Providers:Redux并不特定地需要何種UI框架,可以與Angular、React等等很多UI框架協(xié)同工作。Redux并不是Fractal,一般來說Store被視作Orchestrator。

  • User Event處理器即可以選擇在渲染函數(shù)中聲明,也可以在其他地方進(jìn)行聲明。

Model-View-Update

又被稱作Elm Architecture,上面所講的Redux就是受到Elm的啟發(fā)演化而來,因此MVU與Redux之間有很多的相通之處。MVU使用函數(shù)式編程語言Elm作為其底層開發(fā)語言,因此該架構(gòu)可以被看做更純粹的函數(shù)式架構(gòu)。MVU中的基本組成部分有:

  • Model:定義狀態(tài)數(shù)據(jù)結(jié)構(gòu)的類型

  • View:純函數(shù),將狀態(tài)渲染為界面

  • Actions:以Mailbox的方式傳遞用戶事件的載體

  • Update:用于更新狀態(tài)的純函數(shù)

根據(jù)上述流程,我們可知Elm模式的特性為:

  • 到處可見的層次化組合:Redux只是在View層允許將組件進(jìn)行層次化組合,而MVU中在Model與Update函數(shù)中也允許進(jìn)行層次化組合,甚至Actions都可以包含內(nèi)嵌的子Action

  • Elm屬于Fractal架構(gòu):因?yàn)镋lm中所有的模塊組件都支持層次化組合,即都可以被單獨(dú)地導(dǎo)出使用

Model-View-Intent

MVI是一個(gè)基于RxJS的響應(yīng)式單向數(shù)據(jù)流架構(gòu)。MVI也是Cycle.js的首選架構(gòu),主要由Observable事件流對(duì)象與處理函數(shù)組成。其主要的組成部分包括:

  • Intent:Observable提供的將用戶事件轉(zhuǎn)化為Action的函數(shù)

  • Model:Observable提供的將Action轉(zhuǎn)化為可觀測(cè)的State的函數(shù)

  • View:將狀態(tài)渲染為用戶界面的函數(shù)

  • Custom Element:類似于React Component那樣的界面組件

根據(jù)上述流程,我們可知MVI模式的特性為:

  • 重度依賴于Observables:架構(gòu)中的每個(gè)部分都會(huì)被轉(zhuǎn)化為Observable事件流

  • Intent:不同于Flux或者Redux,MVI中的Actions并沒有直接傳送給Dispatcher或者Store,而是交于正在監(jiān)聽的Model

  • 徹底的響應(yīng)式,并且只要所有的組件都遵循MVI模式就能保證整體架構(gòu)的fractal特性

總結(jié)

以上是生活随笔為你收集整理的GUI应用程序架构的十年变迁:MVC,MVP,MVVM,Unidirectional,Clean的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

91热这里只有精品 | 91污在线观看 | 97超碰在 | 91免费在线播放 | 欧美日韩超碰 | 欧美日韩在线播放 | 麻豆影视在线免费观看 | 91在线区| 中文字幕中文字幕 | 亚洲黄网站| 国产视频精选 | 在线看片成人 | 三级av在线播放 | 欧美在线一级片 | 亚洲国产中文字幕在线观看 | 亚洲欧美日韩一区二区三区在线观看 | 免费日韩视频 | 超碰97.com| 狠狠色丁香久久婷婷综 | 麻豆国产精品va在线观看不卡 | 中文字幕高清视频 | 亚洲精品成人av在线 | 欧美精品亚洲精品日韩精品 | 成年人在线免费看片 | 黄色亚洲片 | 国产一区二区三区高清播放 | 成人一区影院 | 婷婷亚洲激情 | 国产亚洲字幕 | 国产日本在线播放 | 在线观看视频99 | 不卡电影一区二区三区 | 日韩午夜剧场 | 天天操天天射天天爱 | 99久久精品免费看国产 | 精品91久久久久 | 国产成人av电影在线观看 | 亚洲久草在线视频 | 91精选在线观看 | 日韩激情在线视频 | 一级黄色在线视频 | 国产精品一区二区三区在线免费观看 | 精品久久久影院 | 色偷偷88888欧美精品久久久 | 久久久久久久久久久高潮一区二区 | 日韩成人欧美 | 99av在线视频 | 韩日三级在线 | 99在线视频网站 | 狠狠色狠狠色综合系列 | 国产一级在线视频 | 午夜精品导航 | 欧美一级久久 | 人人狠狠| 亚洲精品在线观看不卡 | 九九在线高清精品视频 | a在线一区 | 亚洲国产成人久久 | 国产精品完整版 | av一区在线 | 国产精品久久久久久久午夜 | 国产成人一区二区三区免费看 | 99久久精 | 97综合在线 | 久久亚洲区 | 精品一区二区在线免费观看 | 免费观看久久久 | 91精品免费 | 久久精品www人人爽人人 | 夜夜躁日日躁狠狠躁 | 久久久www成人免费精品张筱雨 | 亚洲成av人片 | 成人超碰在线 | 国内毛片毛片 | 丁香六月婷婷综合 | 亚洲精品乱码白浆高清久久久久久 | 免费又黄又爽 | 91视频免费看网站 | 久久久午夜精品理论片中文字幕 | 久久五月婷婷丁香社区 | 久草在线久草在线2 | 天天操比| 在线看中文字幕 | 成人va在线观看 | 人人爽人人爽人人爽人人爽 | 在线亚洲成人 | 中文字幕在线国产精品 | 久久再线视频 | www.亚洲精品视频 | 超级碰碰碰碰 | 久久r精品 | 免费观看全黄做爰大片国产 | 激情综合网五月激情 | 日日夜夜天天射 | 色在线亚洲| 久久久九色精品国产一区二区三区 | 亚洲九九九在线观看 | 欧美狠狠色 | 黄色一级免费 | 成全免费观看视频 | 亚洲作爱 | 亚洲天堂网站 | 亚洲精选视频免费看 | 成人免费网站在线观看 | 中文字幕av在线电影 | 日本精品久久久一区二区三区 | 亚洲精品88欧美一区二区 | 九九影视理伦片 | av资源在线看 | 日韩高清在线一区二区 | 亚洲视频,欧洲视频 | 免费h在线观看 | 日韩最新av在线 | 久久久国产影院 | 在线亚洲天堂网 | 婷婷六月激情 | 1024在线看片| 国产小视频在线免费观看视频 | 天天天天天天操 | 看毛片网站 | 国产女v资源在线观看 | 最近日本字幕mv免费观看在线 | 啪啪小视频网站 | 999久久久免费精品国产 | 国产精品麻豆99久久久久久 | 欧美日韩视频在线 | 国产福利一区二区三区视频 | 五月综合色婷婷 | 亚洲午夜不卡 | 成人一区在线观看 | 成年人免费电影在线观看 | 国产色拍拍拍拍在线精品 | 免费看片网址 | 免费看三级网站 | 一区二区三区四区不卡 | 婷婷四房综合激情五月 | 日本在线观看一区 | 久久久久久黄色 | 国产精品观看在线亚洲人成网 | 欧美aa一级片| 国产91在线观看 | а天堂中文最新一区二区三区 | 亚州视频在线 | 国产一二三四在线观看视频 | 午夜久久久影院 | av超碰在线观看 | www.天天色.com | 国产粉嫩在线 | 久久久久久99精品 | 极品国产91在线网站 | 国产成人区 | 久操免费视频 | 狠狠躁夜夜躁人人爽超碰91 | 国产精品久久久久av福利动漫 | 综合色站导航 | 99精品视频网 | 永久免费毛片在线观看 | 91网站观看 | 中文字幕在线观看视频免费 | 天天综合网~永久入口 | 天天色官网| 在线观看免费高清视频大全追剧 | 亚州av网站大全 | 蜜桃视频在线观看一区 | 精品免费观看 | 国产私拍在线 | 五月婷婷色播 | 久在线观看 | 欧美在线一级片 | 日韩高清av| 亚洲免费在线看 | 国产精品videoxxxx | 亚洲精品久久久久久中文传媒 | 最新不卡av | 亚洲综合五月天 | 中文字幕日本在线 | 日韩欧美国产视频 | 免费精品人在线二线三线 | 91最新中文字幕 | 91九色在线观看视频 | 粉嫩av一区二区三区四区在线观看 | 欧美性生活免费看 | av免费在线观看网站 | 日韩理论片中文字幕 | 最新日韩中文字幕 | 欧美日韩一二三四区 | 国产精品美女毛片真酒店 | 久久一区二区三区四区 | 最近免费中文字幕大全高清10 | 亚洲国产福利视频 | 久久在线视频精品 | 亚洲一区二区精品 | 日韩欧美精品免费 | 麻豆免费视频观看 | 成人国产精品久久久春色 | 日韩免费一级电影 | 国产精品videossex国产高清 | 2022中文字幕在线观看 | 欧美激情va永久在线播放 | 免费高清看电视网站 | 国产免费亚洲 | 欧美精品色 | 999成人 | 日日夜操| 亚洲国产精品va在线看黑人 | 一级国产视频 | 91超在线 | 少妇自拍av | h网站免费在线观看 | 99视频一区 | 中文字幕日韩无 | 成人97视频一区二区 | 国产黄a三级三级 | 亚洲精品成人av在线 | 人人爱人人添 | 狠狠操狠狠操 | 超碰电影在线观看 | 中文字幕在线观看免费高清完整版 | 日韩精品一区二区三区电影 | 久久久久久久久久久免费av | 午夜精品影院 | 深夜免费福利在线 | 四虎影视精品永久在线观看 | www.com黄色| 成人黄在线 | 亚洲精品在线观看av | 国产精品第一视频 | 亚洲毛片久久 | 色婷婷五| 99爱这里只有精品 | 久久成人人人人精品欧 | 99精品视频在线观看 | 麻豆传媒视频在线播放 | 婷婷日 | 中文字幕网站 | 久久久精品一区二区三区 | 久久人人精 | 久久久久久久久久福利 | 97在线观看免费视频 | 国产精品久久久久一区二区三区 | 久久久久久久久久久久久影院 | 婷婷免费在线视频 | 日韩大片免费观看 | 国产午夜在线 | 999久久国精品免费观看网站 | 欧美尹人 | jizz999| 国产破处视频在线播放 | 91热视频 | 亚洲日本欧美 | 日日操日日插 | 综合国产视频 | 亚洲综合五月天 | 337p日本欧洲亚洲大胆裸体艺术 | 国产精品成人一区二区 | 天天做天天看 | 国产精品久免费的黄网站 | 国产网红在线 | 超碰九九 | 成人毛片在线观看视频 | 久久超碰免费 | 91亚洲精品久久久蜜桃借种 | 国内小视频在线观看 | 亚洲成人资源网 | 丁香婷婷综合五月 | 五月婷婷在线综合 | 欧美极品在线播放 | 日日摸日日碰 | 国产破处视频在线播放 | 亚洲综合成人婷婷小说 | 国产网站在线免费观看 | 日韩精品一区二区三区免费观看 | 国产精品毛片久久 | 国产在线观看一区 | 日韩丝袜| 97看片| 在线观看亚洲精品 | 99久久这里只有精品 | 国产在线视频一区二区三区 | 久久久久欠精品国产毛片国产毛生 | 国产精品久久久久久久久毛片 | 久草在线在线精品观看 | 蜜臀久久99精品久久久久久网站 | 欧女人精69xxxxxx | av电影中文字幕在线观看 | 在线观看黄网站 | 日韩美av在线 | 波多野结衣久久资源 | 日本久久成人中文字幕电影 | 97人人澡人人爽人人模亚洲 | 欧美一二三视频 | 99久久精品免费看国产麻豆 | 亚洲黄色片一级 | 字幕网在线观看 | 懂色av一区二区在线播放 | 国产在线第三页 | 天天天天天天干 | 玖草影院 | 中国老女人日b | 国产不卡视频在线播放 | 制服丝袜在线 | 91精品国产综合久久久久久久 | 91精品视频网站 | 日韩有色 | 国产精品99视频 | 一区二区三区高清不卡 | 日日操日日 | 亚洲天堂在线观看完整版 | 精品一区二区电影 | 亚洲另类交 | 美女网站视频免费都是黄 | 一级黄色免费网站 | 国产中文字幕一区二区三区 | 美女免费黄视频网站 | 99视频国产精品免费观看 | 国产精品福利无圣光在线一区 | 中文字幕在线有码 | 国产午夜精品一区二区三区 | 成人av.com| 中文字幕色在线 | www久久com | 亚洲成av人影院 | 国产特级毛片aaaaaa高清 | 人人插人人干 | 日韩黄色av网站 | 国产亚洲成人精品 | 五月天激情综合 | 麻豆小视频在线观看 | 中文字幕观看在线 | 九九热在线精品视频 | 日韩av线观看 | 国产裸体视频网站 | 在线视频一二三 | 国产 成人 久久 | 日韩成人在线一区二区 | 国产精品久久久久久久久久久久午 | 一级一级一片免费 | 久久久96| 91麻豆精品国产91 | 性色av一区二区 | 日韩手机在线观看 | 日韩精品一区二区三区电影 | 在线成人性视频 | 色视频在线看 | 久久成人综合 | 一级成人免费视频 | 欧美精品做受xxx性少妇 | 91高清视频在线 | 久色婷婷 | 国产精品久久久久久久免费 | 九九精品在线观看 | 91av短视频| 国产精品久久99综合免费观看尤物 | 美女中文字幕 | 亚洲精品久久久久久久不卡四虎 | 免费高清在线观看成人 | 日韩在线视频在线观看 | av免费在线观看网站 | 国产精品亚 | 日韩精品一区二区三区高清免费 | 美女视频是黄的免费观看 | 成人免费在线看片 | 久久福利| 深爱综合网 | 亚洲一级影院 | 国产精品成久久久久三级 | 国产97色在线 | 丁香综合| 免费在线色 | 久久这里只有精品久久 | 国产精品国产三级国产不产一地 | 婷婷精品视频 | 免费开视频 | 国产在线观看 | 国产美腿白丝袜足在线av | 亚洲理论视频 | 四虎成人精品永久免费av九九 | 国产黄色片网站 | 欧美 激情在线 | 精品久久亚洲 | 日本精品在线 | 中字幕视频在线永久在线观看免费 | 免费看污网站 | 玖玖爱免费视频 | 中文字幕在线观看免费观看 | 日本精品免费看 | 婷婷激情网站 | 亚洲婷婷在线视频 | 91精彩视频 | 五月激情久久久 | 亚洲精品在线观看中文字幕 | 欧美成人猛片 | 成年在线观看 | 久久综合九色综合欧美狠狠 | 在线亚洲播放 | 久久艹艹| 久久视频精品在线观看 | 91中文字幕网 | 男女视频91| 激情综合婷婷 | 国产精品去看片 | 日韩aⅴ视频 | 久草资源在线 | 激情五月婷婷丁香 | 99re久久精品国产 | 国产亚洲精品中文字幕 | 一级一片免费视频 | 91九色精品女同系列 | 中文字幕在线一区二区三区 | 六月丁香在线视频 | 波多野结衣久久资源 | 国产免费a| 亚洲成人二区 | 美女一区网站 | 国产精品免费在线视频 | 成年人免费av网站 | 亚洲粉嫩av | 免费精品人在线二线三线 | 久久极品 | 国产成人一区二区三区久久精品 | 国产日产亚洲精华av | 日韩高清激情 | 日韩欧美在线观看一区二区三区 | 在线观看亚洲精品 | 久久一本综合 | 中文字幕永久 | 日韩性xxx| 91最新在线| 天天超碰 | 91完整版在线观看 | 黄色一级片视频 | 欧美成人tv| 黄色免费观看网址 | 日韩亚洲欧美中文字幕 | 成人欧美一区二区三区在线观看 | 亚洲午夜精品一区二区三区电影院 | 婷婷丁香狠狠爱 | 色www.| 色在线观看网站 | 亚洲久草在线视频 | 亚洲女裸体 | 国产精品剧情在线亚洲 | 欧美成人一区二区 | 91热精品 | 日本黄色免费在线 | 一级淫片在线观看 | 日韩免费二区 | 日日爱av | 免费在线观看黄 | 久爱精品在线 | 亚洲人久久久 | 国产97在线视频 | 亚洲人视频在线 | 日韩av看片| 亚洲精品福利视频 | 九九久久精品视频 | 精品超碰| 狠狠操夜夜| 免费h精品视频在线播放 | 日韩和的一区二在线 | 亚洲人成网站精品片在线观看 | 日韩视频专区 | 国产一区二区在线播放 | 日本午夜免费福利视频 | 日韩欧美第二页 | 成年人免费看片网站 | 日韩视频专区 | 亚洲精品麻豆视频 | 在线看片91 | 欧美视频网址 | 在线电影中文字幕 | 午夜视频免费播放 | 久久久久久久久久影视 | 99热这里只有精品免费 | 久草在线视频网站 | 日狠狠| 国产黄色片免费看 | 美女黄视频免费看 | 久久久久久久久久久影视 | 欧美激情视频在线观看免费 | 蜜桃视频成人在线观看 | 久久av一区二区三区亚洲 | 欧美视频国产视频 | 天堂在线免费视频 | 亚洲精品午夜视频 | 美女网站视频免费黄 | 天天综合色天天综合 | 日日日操 | 丰满少妇在线观看 | 国产精品欧美日韩在线观看 | www操操| 久久这里只有精品久久 | 成人v| 国产1级毛片 | 国产精品美女久久久久久久 | 国产精品1区2区3区在线观看 | 国内精品久久久久影院日本资源 | 亚洲综合小说电影qvod | 久久伊人爱| 狠狠狠狠狠狠操 | av中文字幕在线观看网站 | 午夜av网站 | 成人免费中文字幕 | 国产在线视频一区 | 国产一卡二卡在线 | 在线观看免费成人av | 亚洲综合视频网 | 91人人澡人人爽人人精品 | 国产精品久久久久久久7电影 | 欧美久久久久久久 | 天天艹天天干天天 | 91视频免费| av+在线播放在线播放 | 99久久网站 | 国产网站在线免费观看 | 中文字幕一区二区三区在线播放 | 欧美坐爱视频 | 日本久久综合网 | 区一区二区三在线观看 | 久久久国产精品一区二区三区 | av片中文字幕 | 在线观看网站av | 国产99久久九九精品 | 国产亚洲婷婷免费 | 狠狠色丁香婷婷 | 久久在线免费观看视频 | 亚洲高清国产视频 | 97视频免费在线 | 99色在线视频 | 精品国产一二三 | 精品国产乱码久久久久久1区2匹 | 国产人成在线观看 | 一区二区三区在线免费播放 | www.人人草 | 精品一区二区亚洲 | 亚洲精品乱码久久久久久蜜桃欧美 | 亚洲精品国偷拍自产在线观看蜜桃 | 午夜精品一二三区 | 天天操夜夜拍 | 999视频精品 | 成人免费观看视频网站 | 欧美日韩精品在线观看视频 | 国产精品18久久久久白浆 | 国产真实在线 | 成人h动漫在线看 | 亚洲精品视频偷拍 | 久久久私人影院 | 97偷拍视频 | 丝袜美女在线观看 | 91麻豆精品国产91久久久无限制版 | 天天干天天碰 | 亚洲91中文字幕无线码三区 | 一级淫片a | 久久丁香 | 日韩大片在线免费观看 | 蜜臀av性久久久久蜜臀aⅴ涩爱 | 国产精品久久久久一区二区三区共 | 国产精品尤物视频 | 在线观看午夜av | 国产精品成人久久久 | 伊人影院99 | 亚洲午夜小视频 | 中文在线字幕免 | 国产精品成人一区二区三区吃奶 | 国产一区麻豆 | 天天色天天操综合 | 亚洲欧洲一级 | 日日夜精品 | 美女视频a美女大全免费下载蜜臀 | 99精品国产aⅴ | 午夜av日韩| 国产精品爽爽久久久久久蜜臀 | 国产一区播放 | 成人av片免费看 | 成人在线视频论坛 | 婷婷视频在线播放 | 久久国产亚洲 | a成人v在线 | 中文字幕乱码日本亚洲一区二区 | 欧美a级片免费看 | 97视频在线观看视频免费视频 | 久久久久免费看 | 色综合天天综合 | 国产日韩精品一区二区三区在线 | 久久久免费毛片 | 一区二区理论片 | 日日碰夜夜爽 | 啪啪凸凸| 国产123区在线观看 国产精品麻豆91 | 久久美女精品 | av网站大全免费 | 五月婷婷六月丁香 | 亚洲精品视频在线观看免费视频 | 国产又粗又硬又长又爽的视频 | 国内视频在线观看 | 免费看十八岁美女 | 亚洲国产美女久久久久 | 五月天高清欧美mv | 久久综合国产伦精品免费 | 亚洲无人区小视频 | 精品一二| 97超碰免费在线 | 亚洲一区二区视频在线 | 成人一级在线观看 | 探花视频免费观看高清视频 | 玖玖在线视频观看 | 天天操天天操天天操 | 四虎永久精品在线 | 国产剧情一区二区 | 伊人影院在线观看 | 亚洲在线 | 欧美嫩草影院 | 人人舔人人干 | 日日噜噜噜噜夜夜爽亚洲精品 | 国产五十路毛片 | 久久超碰99 | 曰韩精品 | 久久久精品网站 | 色多多视频在线 | 国产精品一区二 | 在线探花 | 中文字幕乱在线伦视频中文字幕乱码在线 | 香蕉久草| 黄色日本免费 | 天天干夜夜 | 黄色三级网站在线观看 | 国产成人精品一区在线 | 久久精品8 | 色综合天天色综合 | 91成人在线观看高潮 | 亚洲第一香蕉视频 | 五月色综合 | 五月天亚洲精品 | 992tv在线观看网站 | 国产精品久久久久久久av电影 | 亚洲成人家庭影院 | 亚洲精品视频在线观看免费 | 手机在线欧美 | 精品伦理一区二区三区 | 国产亚州av | 久久久久9999亚洲精品 | 中文字幕高清在线 | 99久热在线精品视频观看 | 国产福利网站 | 久久国产精品系列 | 国产精品毛片一区视频播不卡 | 一级a性色生活片久久毛片波多野 | av中文字幕网 | 国产一级片网站 | 亚洲欧美日韩在线一区二区 | 欧美精彩视频在线观看 | 18国产精品福利片久久婷 | 久久成人免费视频 | 免费h在线观看 | 日韩在线网址 | 精品影院| 国产在线日韩 | 99精品国产亚洲 | 丁香视频全集免费观看 | www..com黄色片 | 人人干天天射 | 日韩欧美国产激情在线播放 | 久久久夜色| 免费麻豆网站 | 波多野结衣久久资源 | 国产综合精品一区二区三区 | 成人一区二区三区在线 | 黄色成人在线 | www.av免费 | 色婷婷www | 亚洲v欧美v国产v在线观看 | 人人爽久久涩噜噜噜网站 | 久久精品区 | 日韩精品一区二区三区免费观看视频 | 黄色国产精品 | 黄色福利| 午夜国产成人 | 97精品国产 | 免费av网址大全 | 亚洲另类人人澡 | 国产一级在线观看 | 久久国产精品色av免费看 | 国内外成人在线视频 | 日韩欧美精品一区 | 手机成人av在线 | 欧美性色网站 | 一区二区不卡 | 日韩免费久久 | 日韩免费在线视频观看 | 天天激情综合网 | 久久久国产精品电影 | 一级大片在线观看 | 国产午夜三级一二三区 | 18国产精品白浆在线观看免费 | av大全在线看 | 九色视频网 | 国产亚洲激情视频在线 | 成人在线免费观看视视频 | 夜夜躁狠狠躁 | 国产91影视 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 久久精品网站免费观看 | 精品福利视频在线 | 成人一区电影 | 欧美久久久久久久久久久 | 国产亚洲精品久久久久久大师 | 91精品老司机久久一区啪 | 国产精品一区二区在线播放 | 在线免费观看不卡av | 成人免费在线播放视频 | 亚洲影院国产 | 天天操月月操 | 91成人看片 | 91精选在线观看 | 久久久五月婷婷 | 九九久久久久99精品 | av网站免费线看精品 | 日本黄色大片免费看 | 国产精品美女久久久久久 | 久久国产精品99国产 | 久久国产高清视频 | 免费一级特黄毛大片 | 中文字幕永久免费 | 亚洲一级片av | 久久精品一区二区 | 99精品免费久久久久久久久 | 国产不卡在线 | 91精品久久久久久久久久久久久 | 精品久久久久一区二区国产 | 婷婷综合五月天 | 黄色精品一区二区 | av免费在线观看网站 | avlulu久久精品 | 在线中文字幕网站 | 天天干天天拍 | 国产在线国偷精品产拍免费yy | 视频国产一区二区三区 | 99视频精品 | 午夜久久久久久久久久影院 | 精品久久久久久国产 | 日韩免费一区二区在线观看 | 九色琪琪久久综合网天天 | 天天插综合网 | 亚洲无人区小视频 | 一区二区三区手机在线观看 | 久久久99国产精品免费 | 亚洲成人一二三 | 中文字幕成人在线观看 | 亚洲激情p| 久久免费福利视频 | 亚洲精品小视频 | 久久这里有 | 2019免费中文字幕 | 四虎在线影视 | 日韩欧美视频在线观看免费 | 一区二区三区四区五区在线 | 国产亚洲精品久久久网站好莱 | 欧美天天综合网 | 亚洲国产日韩一区 | 国产精品一区在线 | 久久色网站 | 欧美亚洲免费在线一区 | 国产精品都在这里 | 又黄又网站 | 黄色大片免费网站 | 一区二区三区国产精品 | 五月婷影院 | 97超碰国产精品 | 92精品国产成人观看免费 | 精品久久在线 | 天堂va在线高清一区 | 午夜电影av | 成人免费在线视频观看 | 欧美xxxx性xxxxx高清 | 天天干天天拍天天操天天拍 | 欧美另类交在线观看 | 国产免费叼嘿网站免费 | 最近中文国产在线视频 | 国产精品第二十页 | 日韩精品一区二区三区在线播放 | 深夜免费福利在线 | 国产一级免费观看视频 | 9色在线视频 | 九九导航 | 国产精品免费久久久久久久久久中文 | 深夜国产福利 | 99这里有精品 | 成人国产一区二区 | 久久久毛片 | 国产精品久久99精品毛片三a | 日韩免费av网址 | 一级性av | 精品视频在线观看 | 国产成人av网站 | 免费在线观看成人av | 久久99国产一区二区三区 | 韩国一区二区三区视频 | 深夜免费福利网站 | 免费在线观看国产黄 | 亚洲国产日韩欧美 | 久久99亚洲精品 | 99热99热 | 色婷婷99 | 色资源网免费观看视频 | 另类老妇性bbwbbw高清 | 在线播放精品一区二区三区 | 91大神电影 | 欧美aⅴ在线观看 | 久久精品久久99精品久久 | 中字幕视频在线永久在线观看免费 | 干 操 插 | 国产黄色在线网站 | av中文字幕在线播放 | japanesefreesex中国少妇 | 最近中文字幕免费大全 | 欧美日韩国产网站 | 免费看国产曰批40分钟 | 91av超碰| 97精品电影院 | 久久久久99精品国产片 | 免费人人干 | 色综合色综合久久综合频道88 | 日韩资源在线播放 | 欧美一级免费 | 亚洲国产日韩在线 | 国产裸体无遮挡 | 在线99视频 | 亚洲91视频 | 人人澡澡人人 | 亚洲精品视频在线免费 | 精品成人在线 | 国产日韩中文在线 | 波多野结衣网址 | 国产精品一区久久久久 | 免费视频久久久久 | 日韩av免费一区 | 四虎精品成人免费网站 | 中文字幕免费国产精品 | 日韩午夜小视频 | 亚洲闷骚少妇在线观看网站 | 粉嫩av一区二区三区免费 | 欧美淫aaa免费观看 日韩激情免费视频 | 成人av电影免费观看 | 中文字幕有码在线播放 | 国产一级黄色片免费看 | 91在线网址| 亚洲精品视频在线免费播放 | 久久久久成人精品免费播放动漫 | 欧美日韩久久一区 | 久久久久国产精品免费 | 99在线热播精品免费99热 | 黄色影院在线观看 | 国产黄色片免费观看 | 在线看毛片网站 | 日韩午夜在线播放 | 亚洲午夜精品在线观看 | 日韩有码在线观看视频 | 江苏妇搡bbbb搡bbbb | 五月天中文字幕mv在线 | 国产精品成人久久 | 欧美激情在线网站 | 怡红院av久久久久久久 | 精品av网站 | 国产美女精品人人做人人爽 | 久久国产区| 免费福利在线观看 | 亚洲精品美女久久17c | 国产精品第72页 | 丁香六月婷婷开心婷婷网 | 婷婷中文在线 | 欧美日韩超碰 | 国产精品一级在线 | 久久草在线视频国产 | 91精品视频一区 | 国产精品专区h在线观看 | 性色xxxxhd | 国产精品免费成人 | 亚洲精品在线免费观看视频 | 欧美成人手机版 | 久久成人福利 | 日韩精品免费一区 | 久久精品国产99 | 人人网人人爽 | 狠狠干成人综合网 | 日日干美女 | 亚洲一二区精品 | 国产v在线观看 | 最新成人在线 | 亚洲精品视频在线观看免费视频 | 丁香六月中文字幕 | 黄色大片日本 | 在线观看不卡的av | 免费看的黄色录像 | 日韩欧美视频一区二区三区 | 天天干夜夜 | 视频在线观看91 | 亚洲一区精品人人爽人人躁 | 日韩在线观看电影 | 91激情视频在线观看 | 不卡视频在线看 | 美女网色 | 国产亚洲精品久久久久久移动网络 | www.操.com| 日色在线视频 | 波多野结依在线观看 | 在线黄色国产电影 | 久久成人麻豆午夜电影 | 久久综合中文字幕 | 91精品夜夜| 婷婷丁香九月 | 男女精品久久 | 一区二区三区视频网站 | 日本黄色一级电影 | 91视频三区 | 国产精品毛片久久久久久 | 激情欧美一区二区三区免费看 | 91传媒在线播放 | 九九久久久久99精品 | 国产97色| 91精品夜夜 | 久久久久电影网站 | 日韩高清在线观看 | 欧美日韩高清在线一区 | 国产精品一区二区三区视频免费 | 国产只有精品 | 国产一区免费视频 | 午夜在线观看一区 | 欧美日韩精品久久久 | 91在线看| 国内精品久久久久久久影视简单 | 国产精品女视频 | 国内精品久久久久久中文字幕 | 色婷婷电影网 | 狠狠狠狠狠狠天天爱 | 深爱五月激情网 | 亚洲精品男人天堂 | 久操中文字幕在线观看 | 国产精品毛片久久久 | 国产视频在线一区二区 | 中文字幕 在线 一 二 | 黄色三级免费网址 | 91精品视频一区 | 在线免费黄色片 | 色综合久久88色综合天天 | 精品国产乱码久久久久久1区二区 | 日韩性xxxx | 天天操夜夜操天天射 | 久久国产精品一区二区三区四区 | 美女啪啪图片 | 亚洲激情在线播放 | 久久激情五月丁香伊人 | 一区二区三区av在线 | 91成人破解版| 婷婷丁香视频 | a黄在线观看 | 久久免费黄色网址 | 欧美一区二区在线 | 在线免费观看一区二区三区 | 香蕉网址 | 欧美日韩在线精品一区二区 | 天天爽天天射 | 日韩午夜精品福利 | 五月开心婷婷 | 日本精油按摩3 | 成年人电影免费在线观看 | 最近中文字幕 | 日本中文字幕在线看 | 91热| 欧美精品午夜 | 蜜臀av性久久久久蜜臀aⅴ涩爱 | 中文字幕精品一区二区三区电影 | 日韩精品亚洲专区在线观看 | 中文字幕av全部资源www中文字幕在线观看 | 久久久久久久精 | 综合成人在线 | 久久久精品 一区二区三区 国产99视频在线观看 | 国产精品日韩高清 | 久久久久免费网 | 成人午夜影视 | 中文字幕在线播放一区 | 免费久久久 | 香蕉视频网站在线观看 | 黄色aa久久 | av一级片在线观看 | 亚洲电影成人 | 99免费在线播放99久久免费 | 亚洲激情精品 | 国产成人精品日本亚洲999 | 亚洲精品在线视频网站 | 欧美性精品 | 亚洲精品看片 | 天天操夜夜逼 | 美女视频久久久 | 韩国av三级 | 国产区精品在线 | 成人欧美一区二区三区在线观看 | 日韩亚洲在线观看 | 成人一级|