【譯】Android開發(fā)中的MVP架構(gòu)

最近越來越多的人開始談?wù)摷軜?gòu)尺栖。我周圍的同事和工程師也是如此。盡管我還不是特別深入理解MVP和DDD僚饭,但是我們的新項目還是決定通過MVP來構(gòu)建拉讯。

這篇文章是我通過研究和學(xué)習(xí)各種文章以及專題討論所總結(jié)出來的,它包括以下幾點:

  • 為什么越來越多的人開始關(guān)注架構(gòu)昌渤?

  • 首先,MVP是什么?

  • 哪種架構(gòu)才是最好的锣险,MVC,MVVM還是MVP览闰?

  • MVP的利與弊

  • Show me the code!!!代碼展示

不幸的芯肤,這篇文章將不包括:

  • 詳細生動的代碼示例

  • 如何編寫測試代碼

最后,我將告訴你如何更進一步學(xué)習(xí)這些專題压鉴。

順便提一下崖咨,我于上周在當(dāng)?shù)氐囊粋€研討會上對MVP架構(gòu)進行了相關(guān)演講。這篇文章與當(dāng)時的演講內(nèi)容相差無幾油吭。

(譯者注:閱讀更多請點擊原作者PPT

介紹Activity是上帝類

首先击蹲,讓我們思考一下為什么在Android開發(fā)中如此迫切地需要一個清晰的軟件架構(gòu)。

該段摘自“代碼大全第二版”:

避免創(chuàng)建神類婉宰。避免創(chuàng)建無所不知歌豺,無所不能的上帝類。如果一個類需要花費時間從其他類中通過Get()和Set()檢索數(shù)據(jù)(也就是說芍阎,需要深入業(yè)務(wù)并且告訴它們?nèi)绾稳プ觯┦涝允欠駪?yīng)該把這些功能函數(shù)更好的組織到其它類而不是上帝類中。(Riel 1996)

上帝類的維護成本很高谴咸,你很難理解正在進行的操作轮听,并且難以測試和擴展,這就是為什么要避免創(chuàng)建上帝類的黃金法則岭佳。

然而血巍,在Android開發(fā)中,如果你不考慮架構(gòu)的話珊随,Activity類往往會越來越大述寡。這是因為,在Android中叶洞,允許View和其它線程共存于Activity內(nèi)鲫凶。其實最大的問題莫過于在Activity中同時存在業(yè)務(wù)邏輯和UI邏輯。這會增加測試和維護的成本衩辟。

Activity是上帝

這是為什么需要清晰架構(gòu)的原因之一螟炫。不僅會造成Activity的臃腫,還會引起其他問題艺晴,如使Activity和Fragment的生命周期變復(fù)雜昼钻,以及數(shù)據(jù)綁定等掸屡。

什么是MVP?

MVP代表Model然评,View和Presenter仅财。

  • View層負責(zé)處理用戶事件和視圖部分的展示。在Android中碗淌,它可能是Activity或者Fragment類盏求。

  • Model層負責(zé)訪問數(shù)據(jù)。數(shù)據(jù)可以是遠端的Server API贯莺,本地數(shù)據(jù)庫或者SharedPreference等风喇。

  • Presenter層是連接(或適配)View和Model的橋梁宁改。

下圖是基于MVP架構(gòu)的模式之一缕探。View是UI線程。Presenter是View與Model之間的適配器还蹲。UseCase或者Domain在Model層中爹耗,負責(zé)從實體獲取或載入數(shù)據(jù)。依賴規(guī)則如下:

The Dependency Injection

關(guān)鍵是谜喊,高層接口不知道底層接口的細節(jié)潭兽,或者更準(zhǔn)確地說,高層接口不能斗遏,不應(yīng)該山卦,并且必須不了解底層接口的細節(jié),是(面向)抽象的诵次,并且是細節(jié)隱藏的账蓉。

The higher interfaces do not know about the details of the lower ones

依賴規(guī)則?

Uncle Bob的“The Clean Architecture”描述了依賴的規(guī)則是什么逾一。

同心圓將軟件劃分為不同的區(qū)域铸本,一般的,隨著層級的深入遵堵,軟件的等級也就越高箱玷。外圓是實現(xiàn)機制,內(nèi)圓是核心策略陌宿。

這是上面片文章的摘要:

Entities:

  • 可以是一個持有方法函數(shù)的對象

  • 可以是一組數(shù)據(jù)結(jié)構(gòu)或方法函數(shù)

  • 它并不重要锡足,能在項目中被不同應(yīng)用程序使用即可

Use Cases

  • 包含特定于應(yīng)用程序的業(yè)務(wù)規(guī)則

  • 精心編排流入Entity或從Entity流出的數(shù)據(jù)

  • 指揮Entity直接使用項目范圍內(nèi)的業(yè)務(wù)規(guī)則,從而實現(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

  • 所有的細節(jié)所在

  • 如數(shù)據(jù)庫細節(jié)舶得,Web框架細節(jié),等等

MVC弥虐,MVP還是MVVM扩灯?

那么媚赖,哪一個才是最好的呢?哪一個比其他的更優(yōu)秀呢珠插?我能只選擇一個嗎惧磺?

答案是,NO捻撑。

這些模式的動機都是一樣的磨隘。那就是如何避免復(fù)雜混亂的代碼,讓執(zhí)行單元測試變得容易顾患,創(chuàng)造高質(zhì)量應(yīng)用程序番捂。就這樣。

當(dāng)然江解,遠不止這三種架構(gòu)模式设预。而且任何一種模式都不可能是銀彈,他們只是架構(gòu)模式之一犁河,不是解決問題的唯一途徑鳖枕。這些只是方法、手段而不是目的桨螺、目標(biāo)宾符。

利與弊

OK,讓我們回到MVP架構(gòu)上灭翔。剛剛我們了解了什么是MVP魏烫,討論了MVP以及其它熱門架構(gòu),并且介紹了MVC肝箱,MVP和MVVM三者間的不同哄褒。這是關(guān)于MVP架構(gòu)利與弊的總結(jié):

**利

  • 可測試(TDD)

  • 可維護(代碼復(fù)用)

  • 容易Reviewe

  • 信息隱蔽

**弊

  • 冗余的,尤其是小型App開發(fā)

  • (有可能)額外的學(xué)習(xí)曲線

  • 開始編寫代碼之前需要時間成本(但是我敢打賭狭园,設(shè)計架構(gòu)是所有項目開發(fā)所必需的)

Show me the code!!!

這里僅展示了MVP模式的一小段結(jié)構(gòu)读处。如果你想了解更多項目或生動的代碼示例,請參考文章末尾的“鏈接和資源”唱矛。那里有非常豐富和設(shè)計巧妙的示例罚舱,基本都托管在Github上,以便你能clone绎谦,在設(shè)備上運行管闷,并了解工作原理。

首先窃肠,為每一個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 {@link DatePickerDialog}
     */
    void openDatePickerDialog();

    /**
     * Start ListActivity
     */
    void startListActivity();
}

讓我們重寫TopView類,要點如下:

  • TopActivity只是負責(zé)處理事件監(jiān)聽或者展示每個視圖組件

  • 所有的業(yè)務(wù)邏輯必須委托給Presenter類

  • 在MVP中,View和Presenter是一 一對應(yīng)的(在MVVM中是一對多的)

    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 {@link 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類碧囊,最重要的一點是Presenter僅僅是連接View與Model的適配橋梁树灶。比如,TopUseCase#saveCalendarDate()是對TopPresenter細節(jié)隱藏的糯而,同樣對TopView也是如此天通。你不需要關(guān)心數(shù)據(jù)結(jié)構(gòu),也不需要關(guān)心業(yè)務(wù)邏輯是如何工作的熄驼。因此你可以對TopUseCase執(zhí)行單元測試像寒,因為業(yè)務(wù)邏輯與視圖層是分離的。

ublic 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ù)邏輯被實現(xiàn)在Activity類中诺祸,你依然可以執(zhí)行單元測試,只不過這會耗費很多時間祭芦,而且有些復(fù)雜秘车〉僦希可能需要更多的時間來運行App佑女,相反歉备,你本應(yīng)該充分利用測試類庫的性能,如Robolectric咸灿。

總結(jié)

這里沒有萬能藥,而且MVP也僅僅是解決方案之一侮叮,它可以與其他方法協(xié)同使用避矢,同樣,也可以有選擇的用于不同項目囊榜。

鏈接和資源

The Clean Architecture(譯者注:清晰架構(gòu)审胸。譯文) - Uncle Bob

這篇文章由Uncle Bob撰寫,描述了依賴規(guī)則的樣子和它們之間的組件是如何工作的卸勺。我從一開始談?wù)摰哪菑垐D表的靈感就來源于他的文章砂沛,雖然這篇文章不是針對Android開發(fā)的,但是同往常一樣曙求,字里行間蘊藏著很多精辟的道理碍庵,所以,必讀悟狱。

Architecting Android…The clean way? (譯者注:Android中的清晰架構(gòu)静浴。譯文)- Fernando Cejas

我認為這是在探索如何將MVP架構(gòu)到Android開發(fā)專題中最著名,也是最受歡迎的博客挤渐。我也是從他那篇簡單易讀苹享,書寫良好的博客中偶然發(fā)現(xiàn)“MVP”這個名詞的。他的示例代碼托管在Github上浴麻,以便那些想要將MVP架構(gòu)運用到正式App上的Android開發(fā)者clone到得问。

Android Architecture(譯者注:Android架構(gòu)) - Thanos Karpouzis

一個在Android項目中運用MVC囤攀,MVP,MVVM的簡單指導(dǎo)宫纬。我從他的那篇普通卻不平凡的文章中學(xué)到了很多抚岗,尤其是MVC,MVP和MVVM之間的不同哪怔。

Software Design patterns on Android English(譯者注:Android開發(fā)中的軟件設(shè)計模式) - Pedro Vicente Gómez Sánchez

這是一個在Karumi工作的高級Android開發(fā)工程師所講的宣蔚,他解釋了一些MVP架構(gòu)中的設(shè)計模式(如,渲染模式认境,倉庫模式和命令模式)胚委。如果你想深入理解MVC或者MVP,那這就使你要找的叉信。

M?—?Model in MVC, MVP, MVVC in Android(譯者注:MVC亩冬,MVP,MVVC架構(gòu)中Model層在Android中的定義) - Artem Zinnatullin

如果你不還了解Model層中的JSON與SQL硼身,或者不能透徹理解Model層的圖像模型硅急,這篇文章將帶你進一步理解什么是Model層以及為什么Model層獨立于其他層。其中“Model layer is solution”部分很好的解釋了如何通過面向接口的方式編寫測試佳遂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末营袜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子丑罪,更是在濱河造成了極大的恐慌荚板,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吩屹,死亡現(xiàn)場離奇詭異跪另,居然都是意外死亡,警方通過查閱死者的電腦和手機煤搜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門免绿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人擦盾,你說我怎么就攤上這事嘲驾。” “怎么了厌衙?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵距淫,是天一觀的道長。 經(jīng)常有香客問我婶希,道長榕暇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮彤枢,結(jié)果婚禮上狰晚,老公的妹妹穿的比我還像新娘。我一直安慰自己缴啡,他們只是感情好壁晒,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著业栅,像睡著了一般秒咐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碘裕,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天携取,我揣著相機與錄音,去河邊找鬼帮孔。 笑死雷滋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的文兢。 我是一名探鬼主播晤斩,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼姆坚!你這毒婦竟也來了澳泵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤旷偿,失蹤者是張志新(化名)和其女友劉穎烹俗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體萍程,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年兔仰,在試婚紗的時候發(fā)現(xiàn)自己被綠了茫负。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡乎赴,死狀恐怖忍法,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榕吼,我是刑警寧澤饿序,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站羹蚣,受9級特大地震影響原探,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一咽弦、第九天 我趴在偏房一處隱蔽的房頂上張望徒蟆。 院中可真熱鬧,春花似錦型型、人聲如沸段审。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寺枉。三九已至,卻和暖如春绷落,著一層夾襖步出監(jiān)牢的瞬間姥闪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工嘱函, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留甘畅,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓往弓,卻偏偏與公主長得像疏唾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子函似,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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