MVP模式的經典封裝

Android機動車發表於2018-01-19

人之所以能,是相信能。

說到MVP,大家應該都不陌生了,由於其高度解等等優點,越來越多的專案使用這個設計模式。然而,優點雖在,缺點也不少,其中一個就是類多了很多,而且V與P直接要專案通訊,那麼P就得持有V得例項,但如果活動掛掉了,如果沒有對V進行釋放,還有導致記憶體溢位得問題,而且,那麼多的介面函式,看得到人眼花繚亂,也使得很多人在使用這個模式的時候望而尚步。

迴歸正題,最近在進行程式碼重構,決定採用MVP模式進行開發。如果我們不進行封裝,單純地簡單使用MVP來開發,這要就會出現如上的問題,介面和類多而且重複。和別人協同開發也存在問題。那麼對MVP模式進行封裝就顯得很重要了。當然,一千個人中有一千個哈姆雷特,這裡提供一下我的思路,供大家參考。

什麼是MVP模式

MVP模式的經典封裝

MVP模式相當於在MVC模式中又加了一個Presenter用於處理模型和邏輯,將View和Model完全獨立開,在android開發中的體現就是activity僅用於顯示介面和互動,activity不參與模型結構和邏輯。

使用MVP模式會使得程式碼多出一些介面但是使得程式碼邏輯更加清晰,尤其是在處理複雜介面和邏輯時,我們可以對同一個activity將每一個業務都抽離成一個Presenter,這樣程式碼既清晰邏輯明確又方便我們擴充套件。當然如果我們的業務邏輯本身就比較簡單的話使用MVP模式就顯得,沒那麼必要。所以我們不需要為了用它而用它,具體的還是要要業務需要。

其實,簡而言之:view就是UI,model就是資料處理,而persenter則是他們的紐帶。

使用MVP的結構

MVP模式的經典封裝

再對比下MVC

MVP模式的經典封裝

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

封裝思路

MVP模式的經典封裝

上圖介紹:

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供其展示

框架封裝

1、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一直持有,導致記憶體洩漏。

2、view的封裝

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

2.1、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類似就不再累贅。

3、Contract契約類

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

在契約類中定義View的介面,Model的介面。因為Model將資料返給Presenter是使用回撥方式,所以還需要再契約類中定義對應的回撥。

具體看示例吧。

實戰

這裡我們以登入功能模組為例:

1、契約類

/**
 * 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負責資料請求,所以在介面中只定義了登入的方法。

回撥定義了登入成功還是失敗的方法。

2、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實現類,重寫其登入方法既可,將登入介面交給回撥。

3、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例項。

4、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直接操作邏輯了。

再看下整個功能模組的事件流和資料流

MVP模式的經典封裝

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

更多精彩內容,關注我的微信公眾號——Android機動車

MVP模式的經典封裝

相關文章