優化你的程式碼結構 --- MVP

JYcoder發表於2018-03-31

安卓基礎開發庫,讓開發簡單點。
DevRing & Demo地址github.com/LJYcoder/De…

學習/參考地址:
https://www.jianshu.com/p/91c2bb8e6369
http://www.jianshu.com/p/9d40b298eca9
http://blog.csdn.net/lmj623565791/article/details/46596109

MVP是什麼?

Most Valuable Player(最有價值運動球員)? 不不不,雖然我很喜歡看nba,但此MVP非彼MVP。
這裡的MVP是指安卓中的一種開發模式。
它將程式碼整體分為M(Model)、V(View)、P(Presenter)三層。

正經版:
M層(model):資料模型/處理層。負責資料處理、資料提供,如網路請求,資料庫操作
V層(view):檢視展示層。負責介面展示,如Activity,Fragment
P層(presenter):業務邏輯層。負責業務邏輯服務,是V層與M層間的橋樑

MVP示意圖1

你也可以這樣幫助理解下(餐廳版):
M層(model):廚師。負責做菜
V層(view):顧客。點餐吃飯
P層(presenter):服務員。提供下單、上菜等各種服務

MVP示意圖2

與MVC模式的區別:
MVC中,V層與M層是可以互通的,而在MVP中V層與M層是不通的。 按餐廳版來說就是,MVC中顧客可以直接告訴廚師要吃什麼菜,廚師做好後直接把菜端到你面前,而MVP中只能通過服務員來完成點餐到用餐的過程。

使用MVP有什麼好處?

抽象些來說,
因為MVP可以降低程式碼耦合度,提高程式碼的結構清晰度、可讀性、維護性和複用性。

具體些來說,參考JessYan大神的例子來講。
現在有這麼一個需求:Activity中從網路獲取資料然後展示在A控制元件上。
如果不用MVP的話,那就直接把獲取展示等程式碼都寫在Activity中,很快便可以寫完。

但現在需求變動了:
1.要求加入快取功能,如果本地有資料,則先從本地獲取資料,然後再從網路獲取最新資料進行替換
2.要求資料展示在B控制元件上而不是A控制元件。

如果程式碼都是你自己寫的,那改起來還比較輕鬆,但假如是團隊開發,程式碼不是你寫的,你需要花時間把邏輯重新看一遍再開始改,而且如果改錯的話,會影響之前已經寫好的功能。

但使用MVP模式進行開發就不同了。由於它的分工結構清晰,V層僅負責資料展示,P層僅負責業務邏輯,M層僅負責資料獲取/處理。所以改動起來就輕鬆很多。
對於變動的需求1:我們只需在P層加入邏輯判斷(先從本地獲取,再網路獲取),然後M層增加一個從本地獲取資料方法。
對於變動的需求2:我們只需在V層修改獲取到資料後的展示方式,從控制元件A改成控制元件B。

當然還有可複用等優點,這裡就不具體講了。
至於"缺點"嘛,就是會相應地增加程式碼量。有得有失,但得大於失


具體使用

需要寫四個部分:Model層,View層,Presenter層,介面

介面

負責“連線”MVP三層,以便方法呼叫、資料流動。同時也便於進行單元測試。

IView

View層介面,定義View層需實現的方法,P層通過該介面回撥通知View層。

public interface IMovieView {
    //成功獲取到電影資料
    void getMovieSuccess(List<MovieRes> list, int type);
    //獲取電影資料失敗
    void getMovieFail(int status, String desc, int type);
}
複製程式碼

IModel

Model層介面,定義Model層需實現的方法,P層通過該介面呼叫M層獲取/處理資料的方法。

public interface IMovieMoel{
    //請求正在上映的電影資料
    Observable getPlayingMovie(int start, int count);
    ...
}
複製程式碼

Model層

實現IModel介面中的方法,負責資料的獲取/處理。

public class MovieModel implements IMovieMoel{

    @Override
    public Observable getPlayingMovie(int start,int count) {
        //提供資料來源
        return DevRing.httpManager().getService(MovieApiService.class).getPlayingMovie(start, count);
    }
    ...
}
複製程式碼

Presenter層

處理業務邏輯,呼叫M層獲取資料,呼叫V層傳遞展示資料。

public class MoviePresenter {
    private IMovieView mIView;
    private IMovieModel mIModel;

    public MoviePresenter(IMovieView iMovieView, IMovieMoel iMovieMoel) {
        mIView = iMovieView;
        mIModel = iMovieModel;
    }

    /**
     * 獲取正在上映的電影
     *
     * @param start 請求電影的起始位置
     * @param count 獲取的電影數量
     * @param type  型別:初始化資料INIT、重新整理資料REFRESH、載入更多資料LOADMORE
     */
    public void getPlayingMovie(int start, int count, final int type) {

        DevRing.httpManager().commonRequest( mIModel.getPlayingMovie(start, count),
         new CommonObserver<HttpResult<List<MovieRes>>>() {
            @Override
            public void onResult(HttpResult<List<MovieRes>> result) {
                if (mIView != null) {
                    mIView.getMovieSuccess(result.getSubjects(), type);
                }
            }

            @Override
            public void onError(int errType, String errMessage) {
                if (mIView != null) {
                    mIView.getMovieFail(errType, errMessage, type);
                }
            }
        }, RxLifecycleUtil.bindUntilDestroy(mIView));
    }

    ...

     /**
     * 釋放引用,防止記憶體洩露
     */
    public void destroy() {
        mIView = null;
    }
}
複製程式碼

View層

實現IView介面中的方法,對獲取到的資料進行展示

public class MovieFragment implements IMovieView {
    //獲取電影資料成功的網路請求回撥
    @Override
    public void getMovieSuccess(List<MovieRes> list, int type) {
        //成功,對資料進行展示
        ....
    }

    //獲取電影資料失敗的網路請求回撥
    @Override
    public void getMovieFail(int status, String desc, int type) {
        //失敗,介面上做出相應提示
        ...
    }
}
複製程式碼

完成以上幾步後,在View層初始化時,呼叫Presenter層方法即可。

@Override
protected void initData() {
      mPresenter = new MoviePresenter(this, new MovieModel());
      mPresenter.getPlayingMovie(start, mCount, type);
}
複製程式碼

還有一點需注意:
如果Presenter層持有了View層的引用,那麼記得在V層銷燬時,把Presenter層中對View層的引用置null,避免View層回收失敗導致記憶體洩漏。

@Override
public void onDestroy() {
     super.onDestroy();
     if (mPresenter != null) {
          mPresenter.destroy();
          mPresenter = null;
     }
 }
複製程式碼

相關文章