MVP(Model-View-Presenter)框架整理

去年因爲項目重構寫了個 MVP 模式的框架 Demo,一直想整理一下,但上半年確實浪得一(*),現在難得有點空閒,馬上整理一下,作爲回顧,查漏補缺。

相關文章:RxJava + Retrofit 應用整理

接口:

/**
 * @author 小僑
 * @time 2017/7/21  10:17
 * @desc View 層接口/約束類
 */
public interface IView {

    void showLoading();

    void hideLoading();

    void showToast(String message);
}

在 MVP 模式中:View 負責顯示,Presenter 負責邏輯處理,Model 提供數據;且 View 並不直接使用 Model,它們之間的通信是通過 Presenter 來進行的,所有的交互都發生在 Presenter 內部,所以要在 Presenter 中對 View 進行綁定,然後退出時解綁避免內存泄漏。

/**
 * @author 小僑
 * @time 2017/7/21  10:17
 * @desc Presenter 層接口/約束類
 */
public interface IPresenter {

    void attachView(IView view);

    void detachView();
}

因爲獲取數據的內容、方式不同,所以沒有特定的通用方法可以抽取,但是爲求樣式的統一,所以新建 IModel 接口,而且 IModel 可用於標明子類的類型。

/**
 * @author 小僑
 * @time 2017/7/21  11:05
 * @desc Model 層接口/約束類
 */
public interface IModel {

}

基類:

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

/**
 * @author 小僑
 * @time 2017/7/21  10:10
 * @desc activity 基類
 */
public abstract class BaseActivity<P extends BasePresenter> 
                                  extends AppCompatActivity implements IView {
    // 此處的泛型可以理解成用於提醒使用者創建 Presenter 以及規範創建的 Presenter 類型
    // 標記 View 綁定的 Presenter

    protected Context mContext;
    protected P mPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(setContentView());
        mContext = this;
        initView();

        // 先創建才能綁定/解綁
        mPresenter = initPresenter();
        mPresenter.attachView(this);

        initData();
    }

    protected abstract int setContentView();

    protected abstract void initView();

    protected abstract P initPresenter();

    protected abstract void initData();

    @Override
    public void showLoading() {
        showToast("--- 下載中 ---");
    }

    @Override
    public void hideLoading() {
        showToast("+++ 下載完 +++");
    }

    @Override
    public void showToast(String message) {
        // TODO: 如果子類有自己的 showLoading()、hideLoading()、showToast(),可以重寫
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.detachView();
        }
    }
}
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * @author 小僑
 * @time 2017/7/26  17:56
 * @desc fragment 基類(來源:http://www.jianshu.com/p/651146bd0688)
 */
public abstract class BaseFragment<P extends BasePresenter> extends Fragment implements IView {
    // 此處的泛型可以理解成用於提醒使用者創建 Presenter 以及規範創建的 Presenter 類型
    // 標記 View 綁定的 Presenter

    protected Context mContext; // activity 的上下文
    protected P mPresenter;
    private int mLayoutId;

    @Override
    public Context getContext() {
        return mContext;
    }

    public BaseFragment getFragment() {
        return this;
    }

    /**
     * 綁定 activity
     */
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mContext = context;
    }

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

        // TODO: Activity 重新創建時,會重新構建它所管理的 Fragment,原先的 Fragment 的字段值將會全部丟失,
        // 但是通過 Fragment.setArguments(Bundle bundle)方法設置的 bundle 會保留下來。
        // 所以儘量使用 Fragment.setArguments(Bundle bundle)方式來傳遞參數
        Bundle bundle = getArguments();
        if (bundle != null) {
            mLayoutId = bundle.getInt("layoutId");
        }

        mPresenter = initPresenter();
    }

    /**
     * 運行在 onCreate 之後
     * 生成 view 視圖
     */
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(getContentView(), container, false);
        return view;
    }

    /**
     * 運行在 onCreateView 之後
     * 加載數據
     */
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // 由於 fragment 生命週期比較複雜,所以 Presenter 在 onCreateView 創建視圖之後再進行綁定,不然會報空指針異常
        mPresenter.attachView(this);
        initView();
    }

    protected abstract P initPresenter();

    private int getContentView() {
        mLayoutId = setContentView();
        Bundle bundle = new Bundle();
        bundle.putInt("layoutId", mLayoutId);
        setArguments(bundle);
        return mLayoutId;
    }

    protected abstract int setContentView();

    protected abstract void initView();

    @Override
    public void showLoading() {
        showToast("--- 下載中 ---");
    }

    @Override
    public void hideLoading() {
        showToast("+++ 下載完 +++");
    }

    /**
     * 跳轉 fragment
     */
    public void startFragment(Fragment toFragment, String tag) {
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.hide(this).add(android.R.id.content, toFragment, tag);
        fragmentTransaction.addToBackStack(tag);
        fragmentTransaction.commitAllowingStateLoss();
    }

    /**
     * fragment 回退
     */
    public void onBack() {
        getFragmentManager().popBackStack();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.detachView();
        }
    }
}
/**
 * @author 小僑
 * @time 2017/7/21  10:11
 * @desc Presenter 基類
 */
public abstract class BasePresenter<V extends IView, M extends IModel> implements IPresenter {
    // 此處的泛型可以理解成用於提醒使用者 Presenter 是用來操作 Model,然後更新 View 的
    // 標記 Presenter 綁定的 View 和 Model

    protected V mView;
    protected M mModel;

    public BasePresenter() {
        mModel = initModel();
    }

    protected abstract M initModel();

    @Override
    public void attachView(IView view) {
        mView = (V) view;
    }

    @Override
    public void detachView() {
        mView = null;
    }
}

就像上面說的,因爲獲取數據的內容、方式不同,所以沒有特定的通用方法可以抽取,但是爲求樣式的統一,所以新建 IModel 接口,用 IModel 標明子類的類型即可,就不用專門創建 BaseModel。

實現:

/**
 * @author 小僑
 * @time 2017/7/21  10:23
 * @desc movie 頁面合約類
 */
public class MovieContract {

    /**
     * V 視圖層
     */
    interface IView {
        void showData(String s);
    }

    /**
     * P 視圖與邏輯處理的連接層
     */
    interface IPresenter {
        void getData();
    }

    /**
     * M 邏輯(業務)處理層
     */
    interface IModel {
        String downloadData();
    }
}

通過上面的合約類來約束下面的 M、V、P 各自需要、可以做的事情,其實這樣是更有利於維護的:M、V、P 是環環相扣的,在 View 呈現某一效果,都得跟 P、M 聯動;那麼如果要添加、刪除、修改某個特定功能,就可以在合約類中統一修改,實現類受到約束,就必須去響應這些實現。

import android.view.View;
import android.widget.TextView;

/**
 * @author 小僑
 * @time 2017/7/21  10:25
 * @desc movie 頁面 View 層
 */
public class MovieActivity extends BaseActivity<MoviePresenter> 
                                implements MovieContract.IView {

    // 互相關聯
    // MovieActivity 類中:extends BaseActivity<MoviePresenter>
    // MoviePresenter 類中:extends BasePresenter<MovieActivity>

    private TextView mTv;

    @Override
    protected int setContentView() {
        return R.layout.activity_movie;
    }

    @Override
    protected void initView() {
        mTv = (TextView) findViewById(R.id.tv);
    }

    @Override
    protected MoviePresenter initPresenter() {
        return new MoviePresenter();
    }

    @Override
    protected void initData() {
        mPresenter.getData();
    }

    @Override
    public void showData(String s) {
        mTv.setText(s);
    }
}
/**
 * @author 小僑
 * @time 2017/7/21  10:25
 * @desc movie 頁面 Presenter 層
 */
public class MoviePresenter extends BasePresenter<MovieActivity, MovieModel> 
                                implements MovieContract.IPresenter {

    // MoviePresenter<MovieActivity> 已經在 MovieActivity 中 onCreate 時綁定了
    // MoviePresenter<MovieModel> 已經在 MoviePresenter 新建時調用 initModel()方法綁定了
    @Override
    protected MovieModel initModel() {
        return new MovieModel();
    }

    @Override
    public void getData() {
        mView.showData(mModel.downloadData());
    }
}
/**
 * @author 小僑
 * @time 2017/7/21  10:30
 * @desc movie 頁面 Model 層
 */
public class MovieModel implements IModel, MovieContract.IModel {

    @Override
    public String downloadData() {
        return "abc";
    }
}

補充:
因爲之前項目用的網絡請求框架是 RxJava + Retrofit 【RxJava + Retrofit 應用整理】,所以在 Presenter 中使用 Medel 能直接回調 onSuccess 和 onError 方法,但是有一些網絡請求框架沒有這麼方便,所以要自己寫回調接口

/**
 * Model 層獲取數據回調接口
 */
public interface IModelCallBack<T> {

    void onSuccess(T data);

    void onError(String errorMsg);
}
    // Model
    @Override
    public void downloadData(IModelCallBack<String> iModelCallBack) {
        iModelCallBack.onSuccess("Success");
        iModelCallBack.onError("Error");
    }
    // Presenter
    @Override
    public void getData() {
        mModel.downloadData(new IModelCallBack<String>() {
            @Override
            public void onSuccess(String data) {

            }

            @Override
            public void onError(String errorMsg) {

            }
        });
    }

Demo 的 Github地址:
https://github.com/ZhangZeQiao/GeneralFramework.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章