淺談MVP

Model-view-presenter,簡稱MVP,是電腦軟件設計工程中一種對針對MVC模式,再審議後所延伸提出的一種軟件設計模式。java

描述

  • M-Model,數據層android

  • V-View,界面顯示層git

  • P-Presenter,中介者,鏈接Model和View層github

結構圖以下:數據庫

MVP

從圖中能夠看到,View能夠和Presenter進行雙向通訊,Presenter和Model進行單方面通訊,而View和Model是不能直接進行通訊的設計模式

那麼這個通訊是什麼意思呢?我我的理解就是是否持有對方的實例,是否可以調用對方的方法。服務器

因此,這裏就很清晰了。咱們在代碼中想要實現這個架構的話,其實也很簡單,就是讓View和Presenter相互持有以及讓Presenter持有Model的對象。網絡

咱們用簡單的代碼演示一下:架構

public class View {
  private Presenter presenter;
}

public class Presenter {
  private View view;
  private Model model;
}

public class model {}複製代碼

咱們的示例代碼就實現了咱們上面所要求的那些:讓View和Presenter雙向綁定、讓Presenter持有Model的引用。ide

好,到如今,你已經初步瞭解了MVP的規則(也能夠叫作套路)。

有的同窗可能會問,這樣寫的代碼量變多了,爲何要這樣寫?這個咱們後邊再講。

Android中的MVP

既然已經瞭解了MVP的基本規則,那麼咱們就來看一下如何在Android中實現這個架構。

首先,咱們先要了解一下Android中的M、V、P分別都表明着什麼。

View

在Android中,顯示的操做是交給Activity或者Fragment的。因此理所固然的,Activity\Fragment就是MVP中的View了。

既然把Activity或者Fragment看成View層,即顯示層,那麼它的工做應該就是顯示操做,不該該有其餘類型的操做,例如解析數據等等。因此你會發現View中的方法大多就是showXXX()hideXXX()等等。

在個人理解中,一切的邏輯操做都應該在Presenter中處理,可是,實際狀況中,有些特別小的邏輯也能夠在View中直接實現。

Presenter

這裏的Presenter就是處理各類邏輯。例如這樣一個場景:從服務器獲取數據,對數據進行處理,而後顯示在界面上。相信開發者們都遇到過這樣的需求。那麼在咱們的MVP架構中是如何實現的呢?

流程

流程就和圖中顯示的同樣,Model提供數據、Presenter處理數據、View顯示操做

並且命令Model提供數據和命令View顯示數據這兩個操做都應該在Presenter中進行,因此你會發現,MVP的核心就是Presneter中,其餘兩個部分都很單一,就像是工具類同樣,任人調用。

從這個角度出發的話,Presenter必需要持有Model和View的引用,由於只有這樣才能命令它們執行某個操做。

那爲何View還要持有Presneter的引用呢?其實緣由很簡單,由於在Android中大多數事務都是從View中產生的。例如:你想要添加一條數據,點擊了那個+號按鈕,無形中就產生了一個事務。

也就是說不少邏輯都是從View中產生,而後命令Presenter去處理數據(好比檢驗數據是否合理),再而後Presenter再命令Model添加數據。因此,View中也必須持有Presenter的引用,也就是說View和Presenter相互持有。

Model

Model就很簡單了,根據咱們上邊的描述,就知道Model就至關於一個包裝後的數據庫,執行着與數據相關的操做。

另外,我上面說的把Activity、Fragment看成View層只是一種實現方法。還有一種實現方法就是把Activity\Fragment看成Presenter,將UI操做抽到delegate中做爲View層(例如T-MVP)。

實現

接下來咱們就在Android中完完整整地實現MVP架構。

項目的包結構

注意在這裏咱們採起和Google同樣的思路,根據功能進行分包。如圖就是個人項目包結構:

package_structure

  • model 沒錯就是MVP中的M
    • local
      • LocalResponse 本地數據源,通常就是本地數據庫的一些操做
    • remote
      • RemoteResponse 遠程數據源,服務器的一些操做
    • Response 數據源接口,這裏聲明瞭全部對數據的操做
    • ResponseImpl Response的實現類,持有localResponse和RemoteResponse的引用。
  • sign_in 這個就是咱們的登陸功能
    • SignInActivity 咱們這裏的Activity所起的做用就是託管Fragment
    • SignInFragment MVP中的V
    • SignInContract 內有兩個接口,分別是View接口和Presenter接口。
    • SignInPresenter MVP中的P
  • util 工具包
  • BasePresenter Presenter的基類
  • BaseView View的基類
  • SingleFragmentActivity 一個抽象類,把Activity託管一個Fragment的操做封裝起來了。

代碼

SingleFragmentActivity

public abstract class SingleFragmentActivity extends AppCompatActivity {

    protected abstract Fragment createFragment();

    protected void init() {
    }

    /** * 能夠設置一個只有FrameLayout的xml文件做爲默認的Fragment容器, * 若子類有更好的容器能夠重寫該方法,若沒有則能夠直接用默認的容器 * 假設佈局文件名字爲activity_fragment.xml */
    @LayoutRes
    protected abstract int getLayoutResId();

    @IdRes
    protected abstract int getFragmentContainerId();

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

        FragmentManager fm = getSupportFragmentManager();
        Fragment fragment = fm.findFragmentById(getFragmentContainerId());

        if (null == fragment) {
            fragment = createFragment();
            fm.beginTransaction()
                    .add(getFragmentContainerId(), fragment)
                    .commit();
        }
        init();
    }
}複製代碼

BaseView

public interface BaseView<T> {

    void setPresenter(T t);

}複製代碼

BasePresenter

public interface BasePresenter {

    void start();

    void destroy();

}複製代碼

SignInActivity

public class SignInActivity extends SingleFragmentActivity {

    @Override
    protected int getLayoutResId() {
        return R.layout.sign_in_act;
    }

    @Override
    protected int getFragmentContainerId() {
        return R.id.fragment_container;
    }

    @Override
    protected Fragment createFragment() {
        return SignInFragment.newInstance();
    }
}複製代碼

SignInContract

public interface SignInContract {

    interface Presenter extends BasePresenter {

        void signIn(String username, String passwd, Action<Integer> action);

        void signIn(String username, String passwd);

    }

    interface View extends BaseView<Presenter> {
        String getUsername();

        String getPasswd();

        void showToast(String message);
    }

}複製代碼

SignInFragment

public class SignInFragment extends Fragment implements SignInContract.View {
    private static String TAG = "SignInFragment";

    private SignInContract.Presenter mPresenter;

    private View rootView;
    private EditText mEtUsername;
    private EditText mEtPasswd;
    private Button mBtnSignIn;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = new SignInPresenter(this,
                new ResponseImpl(new LocalResponse(), new RemoteResponse()));
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.sign_in_frag, container, false);
        mEtPasswd = (EditText) rootView.findViewById(R.id.et_username);
        mEtUsername = (EditText) rootView.findViewById(R.id.et_passwd);
        mBtnSignIn = (Button) rootView.findViewById(R.id.btn_sign_in);
        initEvent();
        return rootView;
    }

    private void initEvent() {
        mBtnSignIn.setOnClickListener(v ->
                mPresenter.signIn(getUsername(), getPasswd(),
                        result_code -> {
            if (result_code == 0) {
                showToast(getString(R.string.sign_in_success));
            } else {
                showToast(getString(R.string.sign_in_fail));
            }
        }));
    }

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

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

    @Override
    public void setPresenter(SignInContract.Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public String getUsername() {
        return mEtUsername.getText().toString();
    }

    @Override
    public String getPasswd() {
        return mEtPasswd.getText().toString();
    }

    @Override
    public void showToast(String message) {
        Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
    }

    /** * * @return 返回一個指定的Fragment實例 */
    public static Fragment newInstance() {
        return new SignInFragment();
    }
}複製代碼
public class SignInPresenter implements SignInContract.Presenter {
    private static String TAG = "SignInPresenter";

    private SignInContract.View mView;
    private ResponseImpl mResponse;


    public SignInPresenter(SignInContract.View view, ResponseImpl response) {
        mView = view;
        mView.setPresenter(this);
        mResponse = response;
    }

    @Override
    public void start() {
        Log.i(TAG, "start: ");
    }

    @Override
    public void destroy() {
        Log.i(TAG, "destroy: ");
    }

    @Override
    public void signIn(String username, String passwd, Action<Integer> action) {
        mResponse.signIn(username, passwd, action);
    }

    @Override
    public void signIn(String username, String passwd) {
        mResponse.signIn(username, passwd, integer -> {
            if(integer == 0) {
                mView.showToast("Success");
            } else {
                mView.showToast("fail");
            }
        });
    }
}複製代碼

限於篇幅,Model的代碼咱們就不貼了,你們能夠去個人Github上去看完整的demo。

值得注意的地方,咱們的BasePresenter中提供了兩個方法start()destroy(),目的就是要把Presenter的生命週期和Fragment的生命週期綁定在一塊兒。另外,能夠改進的地方是Response的實現類能夠改爲單例模式,以及爲ResponseImpl傳入一個Context參數,因訪問數據庫操做和訪問網絡操做都是須要Context的。

MVP存在的問題

最大的問題就是MVP須要建立太多的接口和實現類。

針對這個問題,有一種解決方法就是使用Android Studio的Template自動生成須要的類和接口,可是這種方法只是解放了雙手,並無減小代碼量。

另外就是,若是某個界面的邏輯很簡單,那麼能夠直接把省略掉Presenter;若是這個界面與數據無關,那麼能夠把Model也省略掉。

Presenter能夠重用,因此不須要每一個Activity或者Fragment都爲其分配一套MVP,能夠幾個Activity或者Fragment共用一個Presenter。

想要了解更多的話,請移步本人的學習筆記,若是以爲有幫助的話,請點一個star✨。

相關文章
相關標籤/搜索