MVP模式的經典封裝

人之因此能,是相信能。android

說到MVP,你們應該都不陌生了,因爲其高度解等等優勢,愈來愈多的項目使用這個設計模式。然而,優勢雖在,缺點也很多,其中一個就是類多了不少,並且V與P直接要項目通訊,那麼P就得持有V得實例,但若是活動掛掉了,若是沒有對V進行釋放,還有致使內存溢出得問題,並且,那麼多的接口函數,看獲得人眼花繚亂,也使得不少人在使用這個模式的時候望而尚步。數據庫

迴歸正題,最近在進行代碼重構,決定採用MVP模式進行開發。若是咱們不進行封裝,單純地簡單使用MVP來開發,這要就會出現如上的問題,接口和類多並且重複。和別人協同開發也存在問題。那麼對MVP模式進行封裝就顯得很重要了。固然,一千我的中有一千個哈姆雷特,這裏提供一下個人思路,供你們參考。設計模式

什麼是MVP模式

MVP模式至關於在MVC模式中又加了一個Presenter用於處理模型和邏輯,將View和Model徹底獨立開,在android開發中的體現就是activity僅用於顯示界面和交互,activity不參與模型結構和邏輯。bash

使用MVP模式會使得代碼多出一些接口可是使得代碼邏輯更加清晰,尤爲是在處理複雜界面和邏輯時,咱們能夠對同一個activity將每個業務都抽離成一個Presenter,這樣代碼既清晰邏輯明確又方便咱們擴展。固然若是咱們的業務邏輯自己就比較簡單的話使用MVP模式就顯得,沒那麼必要。因此咱們不須要爲了用它而用它,具體的仍是要要業務須要。微信

其實,簡而言之:view就是UI,model就是數據處理,而persenter則是他們的紐帶。網絡

使用MVP的結構

再對比下MVC

MVP模式仍是存在一些不足之處的,最大的不足就是類的快速增多,但相對於MVC的臃腫、MVP的高度解耦來講,類的增多可能就灑灑水啦~~~框架

封裝思路

上圖介紹:ide

Contract:契約類,一個功能模塊中View接口、Model接口和請求數據回調統一在對應模塊的Contract中定義,便於管理。函數

ViewInterface: view層接口,定義了view中的UI操做工具

ModelInterface: model層接口,定義了model負責的數據操做方法,如請求接口,操做數據庫等

CallbackInterface: model層操做數據完成後的回調

BasePersenter: Persenter父類,主要是對相關view的獲取,銷燬等操做

View: view層實現類,主要就是Activity或Fragment,負責UI展現和事件響應

Model: model層實現類,就是依據業務,請求對應接口或數據庫,並將結果返給回調CallBack

Persenter: persenter層類,負責業務邏輯處理,view將響應傳給persenter,persenter負責調用model,並將結果返回給view供其展現

框架封裝

一、Presenter的封裝

/**
 * Description: Presenter的根父類
 * Created by jia on 2016/10/27.
 * 人之因此能,是相信能
 */
public abstract class BasePresenter<T> {

    //View接口類型的軟引用
    protected Reference<T> mViewRef;

    public void attachView(T view) {
        //創建關係
        mViewRef = new SoftReference<T>(view);
    }

    protected T getView() {
        return mViewRef.get();
    }

    public boolean isViewAttached() {
        return mViewRef != null && mViewRef.get() != null;
    }

    public void detachView() {
        if (mViewRef != null) {
            mViewRef.clear();
        }
    }
}
複製代碼

先來觀察下這個base類:

先設置一泛型T,T爲與presenter相關的view。BasePresenter中持有一個view的軟引用。

在關聯方法中將view對象傳入,並存入軟引用中,建立獲取、取消關聯和判斷方法。

至於使用軟引用,是爲了防止所持的view都銷燬了,但presenter一直持有,致使內存泄漏。

二、view的封裝

view的封裝,主要是BaseActivity和BaseFragment的封裝。

2.一、BaseActivity

public abstract class BaseActivity<V, T extends BasePresenter<V>> extends FragmentActivity {

    public String TAG = getClass().getSimpleName() + "";

    protected T mPresenter;

    public Context mContext;

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

        initActivityView(savedInstanceState);

        mContext = BaseActivity.this;

        //建立presenter
        mPresenter = createPresenter();

        // presenter與view綁定
        if (null != mPresenter) {
            mPresenter.attachView((V) this);
        }

        findViewById();

        getData();

    }


    /**
     * 關於Activity的界面填充的抽象方法,須要子類必須實現
     */
    protected abstract void initActivityView(Bundle savedInstanceState);

    /**
     * 加載頁面元素
     */
    protected abstract void findViewById();

    /**
     * 建立Presenter 對象
     *
     * @return
     */
    protected abstract T createPresenter();

    protected abstract void getData();

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ...
        if (null != mPresenter) {
            mPresenter.detachView();
        }
    }
}
複製代碼

BaseActivity設置兩個泛型——V和P,明顯地,分別表明對應的View和Presenter。

其持有一個BasePresenter,在onCreated方法中,使用createPresenter方法返回對應的BasePresenter的子類,咱們就可使用了。

這裏注意一下view和presenter的處理:在onCreated中建立Presenter對象,但其內部的view軟引用仍是空;在onResume中關聯view,此時presenter已經持有view的軟引用;固然,還須要在onDestroy中取消關聯。

至於其餘的封裝就再也不介紹了,相信你們確定還有更優的封裝方法。

2.2 BaseFragment

public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {

    public String TAG = getClass().getSimpleName() + "";

    private static final String STATE_SAVE_IS_HIDDEN = "STATE_SAVE_IS_HIDDEN";
    protected T mPresenter;

    //定義一個View用來保存Fragment建立的時候使用打氣筒工具進行的佈局獲取對象的存儲
    protected View view;

    /**
     * 當Fragment進行建立的時候執行的方法
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mPresenter = createPresenter();//建立presenter

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putBoolean(STATE_SAVE_IS_HIDDEN, isHidden());
    }

    /**
     * 這個方法是關於Fragment完成建立的過程當中,進行界面填充的方法,該方法返回的是一個view對象
     * 在這個對象中封裝的就是Fragment對應的佈局
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        view = initFragmentView(inflater);
        return view;
    }

    /**
     * 這個方法當onCreateView方法中的view建立完成以後,執行
     * 在inflate完成view的建立以後,能夠將對應view中的各個控件進行查找findViewById
     */
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        initFragmentChildView(view);
    }

    /**
     * 這個方法是在Fragment完成建立操做以後,進行數據填充操做的時候執行的方法
     */
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initFragmentData(savedInstanceState);
    }


    /**
     * 完成打氣筒操做
     */
    protected abstract View initFragmentView(LayoutInflater inflater);

    /**
     * 進行findViewById的操做
     *
     * @param view 打氣筒生成的View對象
     */
    protected abstract void initFragmentChildView(View view);

    /**
     * 網絡數據填充的操做
     *
     * @param savedInstanceState
     */
    protected abstract void initFragmentData(Bundle savedInstanceState);

    /**
     * 建立Presenter對象
     */
    protected abstract T createPresenter();

    @Override
    public void onResume() {
        super.onResume();
        if (null != mPresenter) {
            mPresenter.attachView((V) this);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (null != mPresenter) {
            mPresenter.detachView();
        }
    }

}
複製代碼

BaseFragment與BaseA相似就再也不累贅。

三、Contract契約類

契約,顧名思義,規範定義,定義功能和模板。

在契約類中定義View的接口,Model的接口。由於Model將數據返給Presenter是使用回調方式,因此還須要再契約類中定義對應的回調。

具體看示例吧。

實戰

這裏咱們以登陸功能模塊爲例:

一、契約類

/**
 * Description:
 * Created by jia on 2017/12/20.
 * 人之因此能,是相信能
 */
public class LoginContract {

    public interface LoginView{

        void onCheckFormatSuccess();

        void onCheckFormatFail(String info);

        void onLoginSuccess(Login login);

        void onLoginFail(String errorInfo);
    }

    public interface LoginModel{
        void login(String name,String password,LoginCallBack callBack);
    }

    public interface LoginCallBack{
        void onSuccess(Login login);

        void onFail(String errorInfo);
    }
}
複製代碼

這裏定義了登陸頁面的view接口、model接口和對應的回調。

在view中,只定義與UI展現的相關方法,如檢查帳號密碼格式成功(失敗)、登陸成功(失敗)等。

model負責數據請求,因此在接口中只定義了登陸的方法。

回調定義了登陸成功仍是失敗的方法。

二、Model實現類

/**
 * Description: 登陸 Model實現類
 * Created by jia on 2017/12/20.
 * 人之因此能,是相信能
 */
public class LoginModelImpl implements LoginContract.LoginModel {

    /**
     * 登陸方法
     * @param name
     * @param password
     * @param callBack
     */
    @Override
    public void login(String name, String password, final LoginContract.LoginCallBack callBack) {
        LoginNetUtils.getInstance().login(name, password, new BaseSubscriber<Login>() {
            @Override
            public void onSuccess(Login login) {
                callBack.onSuccess(login);
            }

            @Override
            public void onFail(String info) {
                callBack.onFail(info);
            }
        });
    }
}
複製代碼

建立Model實現類,重寫其登陸方法既可,將登陸接口交給回調。

三、Presenter

/**
 * Description: 登陸主持類
 * Created by jia on 2017/12/20.
 * 人之因此能,是相信能
 */
public class LoginPresenter extends BasePresenter<LoginContract.LoginView> {

    private LoginModelImpl model;

    public LoginPresenter() {
        model = new LoginModelImpl();
    }

    /**
     * 檢查格式
     *
     * @param name
     * @param password
     */
    public void checkFormat(String name, String password) {
        if (TextUtils.isEmpty(name)) {
            getView().onCheckFormatFail("請輸入用戶名");
        } else if (TextUtils.isEmpty(password)) {
            getView().onCheckFormatFail("請輸入密碼");
        } else if (password.length() < 6 || password.length() > 18) {
            getView().onCheckFormatFail("密碼格式不正確");
        } else {
            getView().onCheckFormatSuccess();
            login(name, password);
        }
    }

    /**
     * 登陸
     *
     * @param name
     * @param password
     */
    public void login(String name, String password) {
        model.login(name, password, new LoginContract.LoginCallBack() {
            @Override
            public void onSuccess(Login login) {
                getView().onLoginSuccess(login);
            }

            @Override
            public void onFail(String errorInfo) {
                getView().onLoginFail(errorInfo);
            }
        });
    }

}
複製代碼

LoginPresenter集成BasePresenter,傳入LoginView最爲泛型T。

內部持有Model實現類對象。

建立兩個方法,一個是檢查格式,一個是登陸。兩個方法就是業務的處理。

如登陸方法,登陸返回後,在回調中獲得數據,也能夠再進行一些邏輯判斷,將結果交給view的對應的方法。

注意這裏使用getView()方法就能夠,由於在父類中getView方法直接返回的對應的view實例。

四、View

/**
 * 登陸界面
 */
public class LoginActivity extends BaseActivity<LoginContract.LoginView, LoginPresenter>
        implements LoginContract.LoginView, View.OnClickListener {

    ...

    @Override
    protected void initActivityView(Bundle savedInstanceState) {
        setContentView(R.layout.activity_login);

    }

    @Override
    protected void findViewById() {
        ...
    }

    @Override
    protected LoginPresenter createPresenter() {
        return new LoginPresenter();
    }

    @Override
    protected void getData() {
    }

    @Override
    public void onCheckFormatSuccess() {
        loading.show();
    }

    @Override
    public void onCheckFormatFail(String info) {
        RxToast.error(mContext, info).show();
    }

    @Override
    public void onLoginSuccess(Login login) {
        ...
    }

    @Override
    public void onLoginFail(String errorInfo) {
       ...
    }

    @Override
    public void onClick(View view) {
        ...
    }
    ...
}
複製代碼

這裏的LoginActivity就是登陸功能模塊的view,其集成BaseActivity,傳入view和presenter泛型。

實現LoginView接口,重寫接口定義好的UI方法。

在createPresenter方法中建立LoginPresenter對象並返回。這樣,就可使用mPresenter直接操做邏輯了。

再看下整個功能模塊的事件流和數據流

大體就是這樣了,有不足的地方你們多提意見。^_^

更多精彩內容,關注個人微信公衆號——Android機動車

相關文章
相關標籤/搜索