淺談Andorid開發中的MVP模式

casoletm 8年前發布 | 14K 次閱讀 Android開發 移動開發

來自: http://www.jcodecraeer.com//a/anzhuokaifa/androidkaifa/2016/0225/3994.html


blob.png


導語:最近公眾號后臺經常收到一些消息,說能不能講一些開發模式,經過思考后,我決定講一講MVP模式。希望對大家能夠有所幫助。并寫了一個簡單的小demo。


背景
看到MVP,大家肯定會想什么是MVP呢?這個我可以肯定的告訴大家MVP(Most Valuable Player)是最有價值球員的意思,這當然是開玩笑了。之所以會出現MVP這種架構模式,是因為我相信大家在開發App時,肯定會發現,Activity的負擔非常重,既要初始化控件,又要寫一些邏輯操作的展示等等,有時候很多Activity中的代碼都充當了Controller和Model的角色,所以你會發現Activity違背單一職責原則,負擔過重。所以,就出現了這么一種架構模式,叫MVP,并不是最有價值球員哦。

什么是MVP架構
MVP就是Model-View-Presenter,MVP是從經典的模式MVC演變而來,它們的基本思想有相通的地方:Controller/Presenter負責邏輯的處理,Model提供數據,View負責顯示。作為一種新的模式,MVP與MVC有著一個重大的區別:在MVP中View并不直接使用Model,它們之間的通信是通過Presenter (MVC中的Controller)來進行的,所有的交互都發生在Presenter內部,而在MVC中View會直接從Model中讀取數據而不是通過 Controller。

在MVC里,View是可以直接訪問Model的!從而,View里會包含Model信息,不可避免的還要包括一些業務邏輯。 在MVC模型里,更關注的Model的不變,而同時有多個對Model的不同顯示,及View。所以,在MVC模型里,Model不依賴于View,但是View是依賴于Model的。不僅如此,因為有一些業務邏輯在View里實現了,導致要更改View也是比較困難的,至少那些業務邏輯是無法重用的。

用流程圖的方式解釋就更清楚了:
blob.png
MVP和MVC的區別,及MVP是如何解決MVC的問題?

MVP架構:

View: 對應于Activity,負責View的繪制以及與用戶交互
Model: 依然是業務邏輯和實體模型
Presenter: 負責完成View于Model間的交互

blob.png

  • View不直接與Model交互,而是通過與Presenter交互來與Model間接交互。

  • Presenter與View的交互是通過接口來進行的。

  • 通常View與Presenter是一對一的,但復雜的View可能綁定多個Presenter來處理邏輯。


MVC架構:

View:對應于布局文件

Model:業務邏輯和實體模型

Controllor:對應于Activity

blob.png

  • View可以與Model直接交互。

  • Controller是基于行為的,并且可以被多個View共享。

  • 可以負責決定顯示哪個View。


總結解釋一下就是說:從MVC到MVP的一個轉變,就是減少了Activity的職責,減輕了它的負擔,簡化了Activity中的代碼和一些操作,將邏輯代碼提取到了Presenter中進行處理,降低了其耦合度。

進一步的解釋:

在MVP里,Presenter完全把Model和View進行了分離,主要的程序邏輯在Presenter里實現。而且,Presenter與具體的View是沒有直接關聯的,而是通過定義好的接口進行交互,從而使得在變更View時候可以保持Presenter的不變,即重用! 不僅如此,我們還可以編寫測試用的View,模擬用戶的各種操作,從而實現對Presenter的測試--而不需要使用自動化的測試工具。 我們甚至可以在Model和View都沒有完成時候,就可以通過編寫Mock Object(即實現了Model和View的接口,但沒有具體的內容的)來測試Presenter的邏輯。 在MVP里,應用程序的邏輯主要在Presenter來實現,其中的View是很薄的一層。因此就有人提出了Presenter First的設計模式,就是根據User Story來首先設計和開發Presenter。在這個過程中,View是很簡單的,能夠把信息顯示清楚就可以了。在后面,根據需要再隨便更改View,而對Presenter沒有任何的影響了。 如果要實現的UI比較復雜,而且相關的顯示邏輯還跟Model有關系,就可以在View和Presenter之間放置一個Adapter。由這個 Adapter來訪問Model和View,避免兩者之間的關聯。而同時,因為Adapter實現了View的接口,從而可以保證與Presenter之間接口的不變。這樣就可以保證View和Presenter之間接口的簡潔,又不失去UI的靈活性。 在MVP模式里,View只應該有簡單的Set/Get的方法,用戶輸入和設置界面顯示的內容,除此就不應該有更多的內容,絕不容許直接訪問Model--這就是與MVC很大的不同之處。


MVP的優點

1.降低耦合度,隱藏數據,Activity中代碼更簡潔

2.模塊職責劃分明顯
3.方便測試驅動開發
4.代碼復用度較高
5.代碼靈活性


MVP架構模式實例

這個實例是根據用戶id獲取用戶信息并展示的一個過程,其中獲取用戶信息用了一個線程進行了模擬獲取。希望大家能夠看懂,并對大家有所幫助。

我們先看一下MVP目錄結構圖

blob.png

1、Model層
首先是一個javabean User實體類

package net.loonggg.mvpdemo.bean;public class User {        private String name;        private String id;        private String sex;        private String age;        public String getName() {                return name;
    }            public void setName(String name) {                this.name = name;
    }        public String getId() {                return id;
    }        public void setId(String id) {                this.id = id;
    }        public String getSex() {                return sex;
    }        public void setSex(String sex) {                this.sex = sex;
    }        public String getAge() {                return age;
    }        public void setAge(String age) {                this.age = age;
    }

}

Model層抽象接口實現:

package net.loonggg.mvpdemo.model;import net.loonggg.mvpdemo.bean.User;public class GetUserInfo implements IGetUser {        @Override
    public void getUserInfo(final int id, final OnUserInfoListener listener) {                // 模擬子線程耗時操作
        new Thread() {                        @Override
            public void run() {                                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }                // 模擬信息獲取成功
                if (id == 1) {
                    User user = new User();
                    user.setName("非著名程序員");
                    user.setAge("26");
                    user.setSex("男");
                    user.setId("1");
                    listener.getUserInfoSuccess(user);
                } else {
                    listener.getUserInfoFailed();
                }
            }
        }.start();
    }

}

Model層抽象接口

package net.loonggg.mvpdemo.model;public interface IGetUser {        public void getUserInfo(int id, OnUserInfoListener listener);
}
package net.loonggg.mvpdemo.model;import net.loonggg.mvpdemo.bean.User;public interface OnUserInfoListener {        void getUserInfoSuccess(User user);            void getUserInfoFailed();
}

2、View層
我們都知道Presenter與View交互是通過接口,所以我們需要定義一個IShowUserView的接口,這個接口封裝的方法基本上都跟視圖展示有關。

package net.loonggg.mvpdemo.view;import net.loonggg.mvpdemo.bean.User;public interface IShowUserView {        void showLoading();        void hideLoading();        void toMainActivity(User user);        void showFailedError();
}

3、Presenter層
Presenter是Model和View之間交互的橋梁,里面有一些業務邏輯的操作。

public class UserInfoPresenter {        private IGetUser iGetUser;        private IShowUserView iShowUserView;        private Handler mHandler = new Handler();        public UserInfoPresenter(IShowUserView iShowUserView) {                this.iShowUserView = iShowUserView;                this.iGetUser = new GetUserInfo();
    }            public void getUserInfoToShow(int id) {
        iShowUserView.showLoading();
        iGetUser.getUserInfo(id, new OnUserInfoListener() {            @Override
            public void getUserInfoSuccess(final User user) {                // 需要在UI線程執行
                mHandler.post(new Runnable() {                    @Override
                    public void run() {
                        iShowUserView.toMainActivity(user);
                        iShowUserView.hideLoading();
                    }
                });
            }            @Override
            public void getUserInfoFailed() {
                mHandler.post(new Runnable() {                    @Override
                    public void run() {
                        iShowUserView.showFailedError();
                        iShowUserView.hideLoading();
                    }
                });
            }
        });
    }

}

4、Activity中的調用

public class MainActivity extends Activity implements IShowUserView {        private Button btn;        private TextView name_tv, age_tv, sex_tv;        private ProgressDialog pd = null;            private UserInfoPresenter userInfoPresenter;        @Override
    protected void onCreate(Bundle savedInstanceState) {                super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        userInfoPresenter = new UserInfoPresenter(this);
        btn = (Button) findViewById(R.id.btn);
        name_tv = (TextView) findViewById(R.id.name_tv);
        age_tv = (TextView) findViewById(R.id.age_tv);
        sex_tv = (TextView) findViewById(R.id.sex_tv);
        pd = new ProgressDialog(this);
        pd.setMessage("正在加載……");

        btn.setOnClickListener(new View.OnClickListener() {            @Override
            public void onClick(View v) {
                userInfoPresenter.getUserInfoToShow(1);
            }
        });
    }            @Override
    public void showLoading() {
        pd.show();
    }            @Override
    public void hideLoading() {
        pd.cancel();
    }           @Override
    public void toMainActivity(User user) {
        name_tv.setText(user.getName());
        age_tv.setText(user.getAge());
        sex_tv.setText(user.getSex());
    }            @Override
    public void showFailedError() {
        Toast.makeText(this, "獲取信息有誤", Toast.LENGTH_SHORT).show();
    }

}

結語:看完實例代碼,有點感覺了吧?俗話說好記性不如爛筆頭,看不如寫,試著自己去寫一個,領會一下其中的精神,相信你會豁然開朗。當然有人說這么做,是不是又多了一層,感覺又麻煩了,是嗎?降低了耦合度,提取了代碼,并增加了復用,代碼更簡潔,其實好處還是很多的。


移動開發者的聚集地,公眾號“非著名程序員”,每天一篇原創技術分享和移動互聯網知識分享,微信公眾號:smart_android

 本文由用戶 casoletm 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!