MVP架構(gòu)模式詳解

一.為什么需要軟件設(shè)計(jì)模式?

我們先來定義什么是好的軟件架構(gòu):
  1. 軟件架構(gòu)上具有明確的分工,各個模塊的功能職責(zé)平衡分配能犯,且明確。
  2. 可測試性犬耻,通常良好的軟件架構(gòu)都具備良好的可測試性踩晶。
  3. 良好的易用性,維護(hù)成本低枕磁。
為什么需要模塊分工渡蜻?

良好的模塊分工,可以大大簡化我們對代碼的理解難度。雖然通過大量的開發(fā)工作茸苇,可以訓(xùn)練我們的大腦去分析越來越復(fù)雜的邏輯排苍,但是人總有極限,而且簡單的邏輯更容易理解学密、不容易出錯淘衙,所以,遵循單一職責(zé)原則腻暮,將復(fù)雜的業(yè)務(wù)邏輯分解彤守。

為什么需要良好的可測試性?

對于深知單元測試好處的開發(fā)者來說哭靖,這并不是一個問題具垫。單元測試可以大大地減少程序運(yùn)行時才能發(fā)現(xiàn)的問題,這通晨钋啵可以節(jié)省「用戶反饋」->「Bug修復(fù)」->「新版本發(fā)布」->「用戶安裝新版本」這個耗時長達(dá)一周以上的過程做修。所以霍狰,程序的可測試性對于程序的穩(wěn)定性是異常重要的抡草。

為什么需要良好的易用性?

毋庸置疑蔗坯,最好的代碼是還沒被寫出來的代碼康震。因此,越少的代碼宾濒,意味著越少的 bugs腿短。這也意味著盡量以最少的代碼實(shí)現(xiàn)相同的功能,并非意味著這個開發(fā)者懶惰绘梦,同時橘忱,也不能不看維護(hù)成本而盲目贊同一個看似聰明的方案。


二.什么是MVP架構(gòu)卸奉?

MVP是單詞Model View Presenter的首字母的縮寫钝诚,分別表示數(shù)據(jù)層、視圖層榄棵、發(fā)布層凝颇,它是MVC架構(gòu)的一種演變。作為一種新的模式疹鳄,MVP與MVC有著一個重大的區(qū)別:在MVP中View并不直接使用Model拧略,它們之間的通信是通過Presenter (MVC中的Controller)來進(jìn)行的,所有的交互都發(fā)生在Presenter內(nèi)部瘪弓,而在MVC中View會直接從Model中讀取數(shù)據(jù)而不是通過 Controller垫蛆。
首先我們先看下傳統(tǒng)的MVC架構(gòu)Model View Controller,我們把業(yè)務(wù)邏輯放到C層(ios的ViewController,android的Activity&Fragment),但是這里會引入另外一個問題袱饭,所有的邏輯都在C層弛随,不可避免的會造成C層非常復(fù)雜,如果項(xiàng)目越來越大宁赤,C層的代碼會更加臃腫舀透,維護(hù)起來也非常麻煩,而且也沒辦法==簡單的==做單元測試决左,試想做一個單元測試我們要加入多少邏輯代碼愕够?


綜上所述我們總結(jié)下,現(xiàn)有的MVC模式存在以下問題:

  1. 視圖與控制器間的過于緊密的連接

視圖與控制器是相互分離佛猛,但卻是聯(lián)系緊密的部件惑芭,視圖沒有控制器的存在,其應(yīng)用是很有限的继找,反之亦然遂跟,這樣就妨礙了他們的獨(dú)立重用。

  1. 視圖對模型數(shù)據(jù)的低效率訪問

依據(jù)模型操作接口的不同婴渡,視圖可能需要多次調(diào)用才能獲得足夠的顯示數(shù)據(jù)幻锁。對未變化數(shù)據(jù)的不必要的頻繁訪問,也將損害操作性能边臼。

  1. 不太友好的單元測試

特別是App上做單元測試的時候很多東西依賴與系統(tǒng)框架哄尔,沒法脫離用戶接口來測試這些邏輯單元。使用MVP對Presenter的測試--不需要使用自動化的測試工具柠并。 我們可以在Model和View都沒有完成時候岭接,就可以通過編寫Mock Object(即實(shí)現(xiàn)了Model和View的接口,但沒有具體的內(nèi)容的)來測試Presenter的邏輯臼予。

基于以上幾點(diǎn)問題鸣戴,就衍生了出了一些軟件設(shè)計(jì)模式如MVP,MVVW粘拾。為什么是MVP窄锅?我們先看下面這張圖

  1. MVP分離了view和model層,Presenter層充當(dāng)了橋梁的角色半哟,View層只負(fù)責(zé)更新界面即可酬滤,這里的View我們要明白只是一個viewinterface,它是視圖的接口寓涨,這樣我們在做單元測試的時候可以非常方便編寫Presenter層代碼盯串。關(guān)于mvp的代碼測試,我們可以參考google給出的代碼戒良,google現(xiàn)在也在推行mvp体捏,為此google發(fā)布了一些案例,大家可參考這里android-architecture
    mvp-clean.png
  1. 厚重的Controller層代碼也得到了釋放,之前我們開發(fā)的時候會對UIViewController几缭、Activity河泳、Fragment編寫很多的業(yè)務(wù)邏輯,盡管大家會將Service層做分離年栓,如net層拆挥,DB層等,但還是無法避免類似的問題某抓,activity uicontroller無法重復(fù)利用是非常難以忍受的纸兔。
  2. 有一點(diǎn)還需要注意,presenter是雙向綁定的關(guān)系否副,因此汉矿,在設(shè)計(jì)的時候就要注意接口和抽象的使用,盡可能的降低代碼的耦合度备禀,這也是mvp的宗旨洲拇。

so,轉(zhuǎn)向mvp吧曲尸!我們先看下MVP幾個單詞的意思赋续,以下是我個人的理解:

  • View: 是顯示數(shù)據(jù)(model)并且將用戶指令(events)傳送到presenter以便作用于那些數(shù)據(jù)的一個接口。View通常含有Presenter的引用队腐。在Android開發(fā)中通常將Activity或者Fragment作為View層蚕捉。
  • Model: 對于Model層也是數(shù)據(jù)層。它區(qū)別于MVC架構(gòu)中的Model柴淘,在這里不僅僅只是數(shù)據(jù)模型。在MVP架構(gòu)中Model它負(fù)責(zé)對數(shù)據(jù)的存取操作秘通,例如對數(shù)據(jù)庫的讀寫为严,網(wǎng)絡(luò)的數(shù)據(jù)的請求等。
  • Presenter:對于Presenter層他是連接View層與Model層的橋梁并對業(yè)務(wù)邏輯進(jìn)行處理肺稀。在MVP架構(gòu)中Model與View無法直接進(jìn)行交互第股。所以在Presenter層它會從Model層獲得所需要的數(shù)據(jù),進(jìn)行一些適當(dāng)?shù)奶幚砗蠼挥蒝iew層進(jìn)行顯示话原。這樣通過Presenter將View與Model進(jìn)行隔離夕吻,使得View和Model之間不存在耦合,同時也將業(yè)務(wù)邏輯從View中抽離繁仁。

三.實(shí)例

接下來我們看一個使用的用例吧涉馅,這個demo相對來說非常簡單,下面是項(xiàng)目的架構(gòu)黄虱,一個Activity稚矿,一個Fragment,Data層主要負(fù)責(zé)獲取App已安裝的應(yīng)用列表,AppListPresenter負(fù)責(zé)業(yè)務(wù)邏輯處理

我們先看下presenter晤揣,viewinterface的結(jié)構(gòu)

!

  • AppListFragment的代碼

  public class AppListFragment extends Fragment implements AppViewInterface {

        private Presenter presenter;

        private List<PackageInfo> packageInfoList = new ArrayList<>();
        private RecyclerView recyclerView;
        private MyAppListRecyclerViewAdapter myAppListRecyclerViewAdapter;

        @Override
        public void showAppList(List<PackageInfo> packageInfos) {
            if (packageInfos.isEmpty())
                return;
            packageInfoList.clear();
            packageInfoList.addAll(packageInfos);
            myAppListRecyclerViewAdapter.notifyDataSetChanged();
        }

        @Override
        public void setPresenter(Presenter presenter) {
            this.presenter = presenter;
        }
    }

代碼比較容易理解桥爽,AppListFragment實(shí)現(xiàn)了AppViewInterface接口,我們需要在Activity中把AppListPresenter和AppViewInterface雙向綁定昧识。

  • 接下來看下AppListPresenter層的代碼钠四,這里只列出了幾個關(guān)鍵方法

    public class AppListPresenter implements Presenter, LoaderManager.LoaderCallbacks<List<PackageInfo>>{

        private AppViewInterface viewInterface;
        private AppClassLoader appClassLoader;
        private LoaderManager loaderManager;

        private final int id = 0;
        public AppListPresenter(AppViewInterface viewInterface, AppClassLoader appClassLoader, 
                        LoaderManager loaderManager) {
            this.viewInterface = viewInterface;
            this.appClassLoader = appClassLoader;
            this.loaderManager = loaderManager;
            viewInterface.setPresenter(this);
        }

        @Override
        public void loadInstallApps() {
            //通過loadmanager提供的方法獲取安裝的應(yīng)用列表
            loaderManager.initLoader(id, null, this);
        }

        @Override
        public void destory() {
            loaderManager.destroyLoader(id);
        }

        @Override
        public void onLoadFinished(Loader<List<PackageInfo>> loader, List<PackageInfo> data) {
            //獲取到已安裝的應(yīng)用列表,調(diào)用AppViewInterface的showAppList方法
            viewInterface.showAppList(data);
        }

        @Override
        public void launchApp(PackageInfo packageInfo) {
            Intent intent = appClassLoader.queryLaunchIntent(packageInfo);
            if (intent != null)
                appClassLoader.getContext().startActivity(intent);
            else
                Toast.makeText(appClassLoader.getContext(), "Can not start the app", Toast.LENGTH_SHORT).show();
        }
    }

關(guān)鍵方法是loadInstallApps跪楞,這個方法在MainActivity的onCreate中調(diào)用

    private Presenter appListPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        AppListFragment appListFragment = AppListFragment.newInstance();
        fragmentTransaction.add(R.id.fm, appListFragment);
        fragmentTransaction.commit();

        appListPresenter = new AppListPresenter(appListFragment, new AppClassLoader(getApplicationContext()), 
                                            getSupportLoaderManager());
        //調(diào)用loadInstallApps
        appListPresenter.loadInstallApps();
    }

首先形导,我們獲取一個AppListFragment的實(shí)例,在AppListPresenter構(gòu)造函數(shù)里面我們傳入AppViewInterface习霹,同時在AppPresenter的構(gòu)造函數(shù)中又將presenter注入到了AppViewInerface里面朵耕,這樣就實(shí)現(xiàn)了Presenter和ViewInerface雙向綁定,之后調(diào)用AppPresenter的loadInstallApps方法淋叶,在onLoadFinished回調(diào)里面又調(diào)用了AppViewInterface的showApps方法阎曹,這樣數(shù)據(jù)就顯示在界面。整個Activity和Fragment的代碼精簡了很多煞檩。

四.缺點(diǎn)

由于對視圖的渲染放在了Presenter中处嫌,所以視圖和Presenter的交互會過于頻繁。還有一點(diǎn)需要明白斟湃,如果Presenter過多地渲染了視圖熏迹,往往會使得它與特定的視圖的聯(lián)系過于緊密。一旦視圖需要變更凝赛,那么Presenter也需要變更了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末注暗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子墓猎,更是在濱河造成了極大的恐慌捆昏,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毙沾,死亡現(xiàn)場離奇詭異骗卜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)左胞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門寇仓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烤宙,你說我怎么就攤上這事遍烦。” “怎么了门烂?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵乳愉,是天一觀的道長兄淫。 經(jīng)常有香客問我,道長蔓姚,這世上最難降的妖魔是什么捕虽? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮坡脐,結(jié)果婚禮上泄私,老公的妹妹穿的比我還像新娘。我一直安慰自己备闲,他們只是感情好晌端,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布粱快。 她就那樣靜靜地躺著料按,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秽澳。 梳的紋絲不亂的頭發(fā)上泻骤,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天漆羔,我揣著相機(jī)與錄音,去河邊找鬼狱掂。 笑死演痒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趋惨。 我是一名探鬼主播鸟顺,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼器虾!你這毒婦竟也來了讯嫂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤曾撤,失蹤者是張志新(化名)和其女友劉穎端姚,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挤悉,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年巫湘,在試婚紗的時候發(fā)現(xiàn)自己被綠了装悲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡尚氛,死狀恐怖诀诊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阅嘶,我是刑警寧澤属瓣,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布载迄,位于F島的核電站,受9級特大地震影響抡蛙,放射性物質(zhì)發(fā)生泄漏护昧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一粗截、第九天 我趴在偏房一處隱蔽的房頂上張望惋耙。 院中可真熱鬧,春花似錦熊昌、人聲如沸绽榛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灭美。三九已至,卻和暖如春昂利,著一層夾襖步出監(jiān)牢的瞬間届腐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工页眯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梯捕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓窝撵,卻偏偏與公主長得像傀顾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子碌奉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

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