Android開發(fā)中的MVP架構(gòu)
最近越來越多的人開始談?wù)摷軜?gòu)。我周圍的同事和工程師也是如此。盡管我還不是特別深入理解MVP和DDD,但是我們的新項(xiàng)目還是決定通過MVP來構(gòu)建。
這篇文章是我通過研究和學(xué)習(xí)各種文章以及專題討論所總結(jié)出來的,它包括以下幾點(diǎn):
- 為什么越來越多的人開始關(guān)注架構(gòu)?
- 首先,MVP是什么?
- 哪種架構(gòu)才是***的,MVC,MVVM還是MVP?
- MVP的利與弊
- Show me the code!!!代碼展示
不幸的,這篇文章將不包括:
- 詳細(xì)生動(dòng)的代碼示例
- 如何編寫測試代碼
***,我將告訴你如何更進(jìn)一步學(xué)習(xí)這些專題。
順便提一下,我于上周在當(dāng)?shù)氐囊粋€(gè)研討會(huì)上對(duì)MVP架構(gòu)進(jìn)行了相關(guān)演講。這篇文章與當(dāng)時(shí)的演講內(nèi)容相差無幾。
介紹~Activity是上帝類~
首先,讓我們思考一下為什么在Android開發(fā)中如此迫切地需要一個(gè)清晰的軟件架構(gòu)。
該段摘自“代碼大全第二版”:
避免創(chuàng)建神類。避免創(chuàng)建無所不知,無所不能的上帝類。如果一個(gè)類需要花費(fèi)時(shí)間從其他類中通過Get()和Set()檢索數(shù)據(jù)(也就是說,需要深入業(yè)務(wù)并且告訴它們?nèi)绾稳プ?,所以是否應(yīng)該把這些功能函數(shù)更好的組織到其它類而不是上帝類中。(Riel 1996)
上帝類的維護(hù)成本很高,你很難理解正在進(jìn)行的操作,并且難以測試和擴(kuò)展,這就是為什么要避免創(chuàng)建上帝類的黃金法則。
然而,在Android開發(fā)中,如果你不考慮架構(gòu)的話,Activity類往往會(huì)越來越大。這是因?yàn)椋贏ndroid中,允許View和其它線程共存于Activity內(nèi)。其實(shí)***的問題莫過于在Activity中同時(shí)存在業(yè)務(wù)邏輯和UI邏輯。這會(huì)增加測試和維護(hù)的成本。
Activity是上帝
這是為什么需要清晰架構(gòu)的原因之一。不僅會(huì)造成Activity的臃腫,還會(huì)引起其他問題,如使Activity和Fragment的生命周期變復(fù)雜,以及數(shù)據(jù)綁定等。
什么是MVP?
MVP代表Model,View和Presenter。
- View層負(fù)責(zé)處理用戶事件和視圖部分的展示。在Android中,它可能是Activity或者Fragment類。
- Model層負(fù)責(zé)訪問數(shù)據(jù)。數(shù)據(jù)可以是遠(yuǎn)端的Server API,本地?cái)?shù)據(jù)庫或者SharedPreference等。
- Presenter層是連接(或適配)View和Model的橋梁。
下圖是基于MVP架構(gòu)的模式之一。View是UI線程。Presenter是View與Model之間的適配器。UseCase或者Domain在Model層中,負(fù)責(zé)從實(shí)體獲取或載入數(shù)據(jù)。依賴規(guī)則如下:
The Dependency Injection
關(guān)鍵是,高層接口不知道底層接口的細(xì)節(jié),或者更準(zhǔn)確地說,高層接口不能,不應(yīng)該,并且必須不了解底層接口的細(xì)節(jié),是(面向)抽象的,并且是細(xì)節(jié)隱藏的。
The higher interfaces do not know about the details of the lower ones
依賴規(guī)則?
Uncle Bob的“The Clean Architecture”描述了依賴的規(guī)則是什么。
同心圓將軟件劃分為不同的區(qū)域,一般的,隨著層級(jí)的深入,軟件的等級(jí)也就越高。外圓是實(shí)現(xiàn)機(jī)制,內(nèi)圓是核心策略。
這是上面片文章的摘要:
Enitities:
- 可以是一個(gè)持有方法函數(shù)的對(duì)象
- 可以是一組數(shù)據(jù)結(jié)構(gòu)或方法函數(shù)
- 它并不重要,能在項(xiàng)目中被不同應(yīng)用程序使用即可
Use Cases
- 包含特定于應(yīng)用程序的業(yè)務(wù)規(guī)則
- 精心編排流入Entity或從Entity流出的數(shù)據(jù)
- 指揮Entity直接使用項(xiàng)目范圍內(nèi)的業(yè)務(wù)規(guī)則,從而實(shí)現(xiàn)Use Case的目標(biāo)
Presenters Controllers
- 將Use Case和Entity中的數(shù)據(jù)轉(zhuǎn)換成格式最方便的數(shù)據(jù)
- 外部系統(tǒng),如數(shù)據(jù)庫或網(wǎng)頁能夠方便的使用這些數(shù)據(jù)
- 完全包含GUI的MVC架構(gòu)
External Interfaces, UI, DB
- 所有的細(xì)節(jié)所在
- 如數(shù)據(jù)庫細(xì)節(jié),Web框架細(xì)節(jié),等等
MVC,MVP還是MVVM?
那么,哪一個(gè)才是***的呢?哪一個(gè)比其他的更優(yōu)秀呢?我能只選擇一個(gè)嗎?
答案是,NO。
這些模式的動(dòng)機(jī)都是一樣的。那就是如何避免復(fù)雜混亂的代碼,讓執(zhí)行單元測試變得容易,創(chuàng)造高質(zhì)量應(yīng)用程序。就這樣。
當(dāng)然,遠(yuǎn)不止這三種架構(gòu)模式。而且任何一種模式都不可能是銀彈,他們只是架構(gòu)模式之一,不是解決問題的唯一途徑。這些只是方法、手段而不是目的、目標(biāo)。
利與弊
OK,讓我們回到MVP架構(gòu)上。剛剛我們了解了什么是MVP,討論了MVP以及其它熱門架構(gòu),并且介紹了MVC,MVP和MVVM三者間的不同。這是關(guān)于MVP架構(gòu)利與弊的總結(jié):
**利
- 可測試(TDD)
- 可維護(hù)(代碼復(fù)用)
- 容易R(shí)eviewe
- 信息隱蔽
**弊
- 冗余的,尤其是小型App開發(fā)
- (有可能)額外的學(xué)習(xí)曲線
- 開始編寫代碼之前需要時(shí)間成本(但是我敢打賭,設(shè)計(jì)架構(gòu)是所有項(xiàng)目開發(fā)所必需的)
show me the code!!!
這里僅展示了MVP模式的一小段結(jié)構(gòu)。如果你想了解更多項(xiàng)目或生動(dòng)的代碼示例,請參考文章末尾的“鏈接和資源”。那里有非常豐富和設(shè)計(jì)巧妙的示例,基本都托管在Github上,以便你能clone,在設(shè)備上運(yùn)行,并了解工作原理。
首先,為每一個(gè)View定義接口。
- /**
- * Interface classes for the Top view
- */
- public interface TopView {
- /**
- * Initialize the view.
- *
- * e.g. the facade-pattern method for handling all Actionbar settings
- */
- void initViews();
- /**
- * Open {<a href="http://www.jobbole.com/members/57845349">@link</a> DatePickerDialog}
- */
- void openDatePickerDialog();
- /**
- * Start ListActivity
- */
- void startListActivity();
- }
讓我們重寫TopView類,要點(diǎn)如下:
- TopActivity只是負(fù)責(zé)處理事件監(jiān)聽或者展示每個(gè)視圖組件
- 所有的業(yè)務(wù)邏輯必須委托給Presenter類
- 在MVP中,View和Presenter是一 一對(duì)應(yīng)的(在MVVM中是一對(duì)多的)
- public class TopActivity extends Activity implements TopView {
- // here we use ButterKnife to inject views
- /**
- * Calendar Title
- */
- @Bind(R.id.calendar_title)
- TextView mCalendarTitle;
- private TopPresenter mTopPresenter;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_top);
- ButterKnife.bind(this);
- // Save TopPresenter instance in a meber variable field
- mTopPresenter = new TopPresenter();
- mTopPresenter.onCreate(this);
- }
- /*
- * Overrides method from the {<a href="http://www.jobbole.com/members/57845349">@link</a> TopView} interfaces
- */
- @Override
- public void initViews() {
- // Actionbar settins
- // set event listeners
- }
- @Override
- public void openDatePickerDialog() {
- DatePickerFragment.newInstance().show(getSupportFragmentManager(),
- DatePickerFragment.TAG);
- // do not write logic here... all logic must be passed to the Presenter
- mTopPresenter.updateCalendarDate();
- }
- @Override
- public void startListActivity() {
- startActivity(new Intent(this, ListActivity.class));
- }
- }
這是Presenter類,最重要的一點(diǎn)是Presenter僅僅是連接View與Model的適配橋梁。比如,TopUseCase#saveCalendarDate()是對(duì)TopPresenter細(xì)節(jié)隱藏的,同樣對(duì)TopView也是如此。你不需要關(guān)心數(shù)據(jù)結(jié)構(gòu),也不需要關(guān)心業(yè)務(wù)邏輯是如何工作的。因此你可以對(duì)TopUseCase執(zhí)行單元測試,因?yàn)闃I(yè)務(wù)邏輯與視圖層是分離的。
- public class TopPresenter {
- @Nullable
- private TopView mView;
- private TopUseCase mUseCase;
- public TopPresenter() {
- mUseCase = new TopUseCase();
- }
- public void onCreate(@NonNull TopView topView) {
- mView = topView;
- // here you call View's implemented methods
- mView.initViews();
- }
- public void updateCalendarDate() {
- // do not forget to return if view instances is null
- if (mView == null) {
- return;
- }
- // here logic comes
- String dateToDisplay = mUseCase.getDateToDisplay(mContext.getResources());
- mView.updateCalendarDate(dateToDisplay);
- // here you save date, and this logic is hidden in UseCase class
- mUseCase.saveCalendarDate();
- }
- }
當(dāng)然,盡管業(yè)務(wù)邏輯被實(shí)現(xiàn)在Activity類中,你依然可以執(zhí)行單元測試,只不過這會(huì)耗費(fèi)很多時(shí)間,而且有些復(fù)雜??赡苄枰嗟臅r(shí)間來運(yùn)行App,相反,你本應(yīng)該充分利用測試類庫的性能,如Robolectric。
總結(jié)
這里沒有***藥,而且MVP也僅僅是解決方案之一,它可以與其他方法協(xié)同使用,同樣,也可以有選擇的用于不同項(xiàng)目。