天天看點

關于Android MVP模式的思考

這一周對現有的Android項目進行了架構重構,使用MVP模式來重新建構整個項目和包結構。今天就來總結一下我在這個過程中了解和實踐吧。

MVP概述

MVP是指Model,View和Presenter的縮寫,是MVC模式的一種改進版。MVP是一種非常适合Android應用的開發模式,它将把邏輯相關代碼從presentation Layer中分離出去,是以,所有界面應該顯示什麼和界面如何顯示這些是互相分離的, 在理想狀态下,MVP模式可以随意切換視圖顯示的形式。

 但是,需要首先聲明的是,MVP并不是一個架構模式,它隻負責presentation Layer。在Android項目中,還可能存在Domain Layer和Data Layer,它們分别負責業務邏輯和資料存儲。關于三個layer的讨論,可以檢視

clear Architecture

mvp.png

The presenter

The presenter充當的是view和model子產品中間人的角色。它從model子產品獲得資料并發送給view子產品。但是不同于MVC,presenter子產品也決定使用者和界面互動時界面如何響應。

The View

View子產品,一般由Activity或者Fragment實作,每個View執行個體一般都包含一個presenter執行個體的引用,并負責執行個體化presenter。理想情況下,你可以使用dagger來實作依賴注入。view的任務就是當使用者操作界面時,調用presenter執行個體的功能來進行響應。

The Model

在設計良好的分層架構中,model應該隻是domain layer和業務邏輯的入口,比如說,

clear architecture

。但是你也可以把所有界面顯示無關的邏輯都作為model。

MVP示例

關于MVP架構的執行個體有很多,比如

google官方架構

。這裡我就給出一下我重構項目一個子產品的類圖,然後大緻說一下實作。

webwxgetmsgimg.jpg

 首先定義了

BaseView

BasePresenter

兩個接口,實作了

BaseActivity

BaseFragment

兩個基礎類。需要注意的是,因為每個View都需要持有一個特定類型的presenter對象,是以,在

BaseView

定義時,使用了範型。除此之外,你可以在

initPresenter

函數中構造出所需要的presenter對象。

showLoading

stopLoading

函數是所有視圖進行網絡資料加載時所需要的方法,其具體使用場景後邊可以看到,這裡這兩個接口是在

BaseActivity

中實作的。

public interface BaseView<T> {
    void showLoading();
    void stopLoading();
    void initPresnter();
    T getPresenter();
}

public interface BasePresenter {

}

public class BaseActivity extends Activity{
    public void showLoading() {
    }

    public void stopLoading() {
    }
}

public class BaseFragment extends Fragment {
    public void showLoading() {

    }

    public void stopLoading() {

    }
}
           

 然後是data子產品中的相關類和接口的定義。我們在

DataContract

接口中定義所有相關的view和data。

public interface DataContract {
    interface DataView extends BaseView<DataPresenter> {
        void showFragment(BaseFragment fragment);
    }
    interface DataPresenter extends BasePresenter {
        void switchToDataList();
        void switchToDataTree();
    }
    interface DataListView extends BaseView<DataListPresenter> {
        void showDataList(List<String> data);
    }
    interface DataListPresenter extends BasePresenter {
        void loadDataList();
    }
    interface DataTreeView extends BaseView<DataTreePresenter> {}
    interface DataTreePresenter extends BasePresenter {}
}
           

 在

DataContract

接口中,我們可以清晰的看到這個子產品中所有的view和presenter類型和他們的接口。這樣無疑可以幫助其他程式員快速了解本子產品的業務邏輯。下面,我們來看一下

DataListFragmnet

DataListPresenter

的實作。

public class DataListFragment extends BaseFragment implements DataContract.DataListView {
    private DataContract.DataListPresenter mPresenter;
    @Override
    public void initPresnter() {
        mPresenter = new DataListPresenter(this);
    }
    @Override
    public DataContract.DataListPresenter getPresenter() {
        return mPresenter;
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initPresnter();
        //需要加載資料或者接收到界面點選事件時,調用presenter接口進行初始化或者響應。
        mPresenter.loadDataList();
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_data_list, container, false);
    }
    @Override
    public void showDataList(List<String> data) {
        //将資料傳給adapter等資料展示的視圖
    }
}

public class DataListPresenter implements DataContract.DataListPresenter {
    private DataContract.DataListView mView;
    public DataListPresenter(DataContract.DataListView view) {
        super();
        this.mView = view;
    }

    @Override
    public void loadDataList() {
        //調用baseview的接口,這些接口實作在baseFragment或者BaseActivity中
        mView.showLoading();
        //getdata from network
        mView.stopLoading();
        mView.showDataList(Arrays.asList("a","b","c"));
    }
}
           

 上述代碼中有很多可以好好思考的地方。首先是

showLoading

stopLoading

這兩個函數。這兩個函數接口定義在

BaseView

,實作在

BaseFragment

,是以當

DataListFragment

繼承了

BaseFragment

并且實作了

BaseView

時,它并不需要再次定義上述兩個接口,畢竟在一個app中,網絡加載時的laoding動畫一般都是一緻的。然後是view和presenter之間函數調用的問題。首先,view在需要初始化或者需要處理點選事件時,需要調用presenter的函數,而不是自己來進行處理。而且二者之間的資料傳遞必須是presenter調用view的接口傳遞給view資料,而不是view調用presenter接口獲得資料然後自己展示。

MVP實踐中的問題和思考

 在實作MVP的過程中,我發現有些子產品的代碼很容易重構為MVP模式,而有些子產品的代碼卻需要花費大量的時間。那些難重構的子產品的所有代碼都寫在了Activity類中,而那些容易重構的子產品已經做了邏輯代碼和視圖代碼的分離。可以說,MVP模式不就是将視圖和邏輯分離嘛,你不使用MVP模式,也可以按照自己的了解進行良好的設計。但是,當一個團隊開發項目時,問題就變得不同了。不同程式員的水準有一定差異,你無法通過他們的水準來確定他們代碼都是良好設計的。你必須通過MVP模式來強制他們去思考如何分離界面和邏輯,否則,他們估計會把所有代碼都寫在Activity類中。我認為這是所謂模式和架構的第一層好處,也就是強迫程式員使用一個種良好的思維方式去思考如何進行代碼組織。

 第二點的思考是,模式其實就是良好的代碼思考的結晶,如何寫出内聚的函數?如何寫入内聚的類?如何讓子產品之間耦合小?如何命名?很多模式都是對這些問題思考結果的總和。

 如果大家對MVP或者移動開發架構有什麼思考,歡迎在文章底下進行評論。

繼續閱讀