1. 程式人生 > >Android中MVC架構和MVP架構的實踐詳解 通俗易懂的Demo

Android中MVC架構和MVP架構的實踐詳解 通俗易懂的Demo

前言

相信從事軟體開發的夥計們肯定熟悉或者聽說過專案架構,比如要新開發一個APP或者Web專案,首先考慮的就是專案需要設計什麼樣的架構,MVC還是MVP呢?MVC和MVP具體是怎麼體現的,有哪些優點,哪些缺點呢?

為什麼需要架構設計

假如我們不需要架構設計,那我們的APP專案程式碼會寫成什麼樣,會不會Activity裡有載入View,初始化View的程式碼; 如果需要跟伺服器的交流,那也會有網路操作程式碼;如果有需要從本地資料庫存取資料,那麼也會有資料庫操作程式碼; 如果有一些操作大家都需要呼叫到,比如字串操作,圖片操作,記憶體操作,那麼這個activity有一份這樣的程式碼,那個activity也有這樣一份程式碼

顯然這種情況,如果一直是單人開發,且專案比較小,迭代週期慢,那問題不大,反正就一個人搞,挺好的;但是如果是團隊合作開發,且是快速迭代的產品,那每個人面對這樣的程式碼,怎麼開發,怎麼維護,那將是一個巨大的痛苦啊。

所以這時候就需要引入架構設計使得程式模組化或者說元件化,降低程式碼的耦合性,加入面向介面的開發,使得開發人員只需要專注自己的開發內容,更加利於後期維護,提高程式開發效率,也方便後續的測試及問題的定位

MVC

MVC全稱Model View Controller,是模型(model)-檢視(view)-控制器(controller)的縮寫,一種軟體設計典範;它用一種業務邏輯,資料處理,介面顯示相分離的方法去組織程式碼,將業務邏輯聚集到一個部件裡面,在改進和個性化定製介面及使用者互動的同時,不需要重新編寫業務邏輯。其中M層處理資料,業務邏輯等;V層處理介面的顯示結果;C層起到橋樑的作用,來控制V層和M層通訊以此來達到分離檢視顯示和業務邏輯層。

劃分

在Android開發過程中,比較流行的專案框架曾經是MVC模式,採用這種模式的好處就是便於UI介面的顯示和業務邏輯的分離,具體如下

  • 模型層(Model) 這裡一般就是業務邏輯真正的處理部分了,通常做一些資料處理工作,比如資料庫操作,網路請求,複雜演算法,耗時操作等,用來描述業務邏輯怎麼組合,同時也為資料定義業務規則
  • 檢視層(View) 這裡是資料顯示的部分,介面就是各種UI元件(XML佈局或者Java自定義控制元件物件),只負責展示資料,同時接收控制器傳過來的結果
  • 控制層(Controller) 這裡通常就交給Activity和Fragment了,這裡就會處理使用者互動問題;比如檢視發請求給控制器,由控制器來選擇相應的模型來處理;模型返回結果給控制器,由控制器選擇合適的檢視展示

在Android中的理解

在Android的MVC中,View大多數情況是xml檔案,我們通常對View所做的操作邏輯都是寫在Activity或者Fragment中,比如點選事件,長按事件等,這樣請求事件傳送到Controller中,比如點選事件是下載,那麼Controller就會將事件轉發到Model層去請求資料,Model獲取資料後就會通過訊息訂閱釋出機制或者介面去更新View,更新View的操作也是在Activity或者Fragment中操作,這樣一個閉環就形成了,View -> Controller -> Model - > View

但是這裡View也是可以直接訪問Model的,要知道我們自定義的View是Java類,在這個類裡是可以直接進行訪問Model操作獲取資料的,就是繞開了Controller,也是一個閉環,View -> Model - > View

樣例

說了這麼多,那到底這種模式對映到Android開發中是什麼樣的呢?

我們就寫一個簡單的業務功能,情景是使用者點選螢幕上的按鈕,想要獲取自己的使用者資訊,然後進行網路請求,獲取資料後展示在螢幕上

編寫View層

也就是XML檔案

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn_getuser"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/get_usermsg"
        android:textColor="@android:color/white"
        android:textSize="@dimen/size_14"
        android:paddingLeft="@dimen/paddind_8"
        android:paddingRight="@dimen/paddind_8"
        android:background="@color/colorAccent"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/margin_20"/>
    
    <TextView
        android:id="@+id/tv_msg"
        android:layout_below="@+id/btn_getuser"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingLeft="@dimen/paddind_8"
        android:paddingRight="@dimen/paddind_8"
        android:layout_marginTop="@dimen/margin_20"/>

</RelativeLayout>

裡面需要引用到資原始檔diemens.xml,strings.xml,colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="margin_20">20dp</dimen>
    <dimen name="size_14">14sp</dimen>
    <dimen name="paddind_8">8dp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<resources>
    <string name="get_usermsg">獲取使用者資訊</string>
</resources>

編寫Model層

需要一個物件封裝使用者資訊

/**
 * @Description TODO(封裝使用者資訊)
 * @author mango
 * @Date 2018/10/25 10:04
 */
public class User {

	private String userId;
    private String userName;
    private String age;
    private String sex;
    private String job;

	public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
    
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }
}

我們這裡使用面向介面方式來開發網路請求操作,先定義一個介面

/**
 * @Description TODO(關於使用者資料操作介面)
 * @author cxy
 * @Date 2018/10/25 10:08
 */
public interface UserModel {

    /**
     * @Description TODO(獲取使用者資料)
     * @author cxy
     * @parame userId 使用者索引
     * @parame listener 回撥介面
     */
    void getUserMsg(String userId , onUserListener listener);
}

然後定義一個回撥介面

/**
 * @Description TODO(使用者操作回撥介面)
 * @author cxy
 * @Date 2018/10/25 10:12
 */
public interface onUserListener {

    public static final int USERID_IS_EMPTY = 1;
    public static final int USER_IS_INVALID = 2;

    void onBefore();

    void onSuccess(User user);

    void onError(int errorID);
}

介面實現類

/**
 * @Description TODO(使用者操作具體實現類)
 * @author cxy
 * @Date 2018/10/25 10:20
 */
public class UserModelImpl implements UserModel {

    @Override
    public void getUserMsg(String userId, final onUserListener listener) {

        if (TextUtils.isEmpty(userId)) {
            listener.onError(USERID_IS_EMPTY);
            return;
        }

        if (listener == null) return;

        listener.onBefore();
        //這裡模擬網路耗時操作,實際開發中絕對不要這樣直接new Thread
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                User user = new User();
                user.setUserId("12");
                user.setUserName("凱撒");
                user.setAge("35");
                user.setSex("0");
                user.setJob("碼農");

                if (user == null) {
                    listener.onError(USER_IS_INVALID);
                } else {
                    listener.onSuccess(user);
                }

            }
        }).start();

    }

}

這裡有人可能會說為什麼要定義介面做呢,這麼麻煩,乾脆寫個公共的網路操作類,提供一個getUserMsg方法呼叫就行了;為什麼這種方式不可取呢,聽我慢慢道來

假如這裡網路操作是用原生HttpUrlConnection開發的,然後哪天需要改成OKHttp,難道去修改getUserMsg方法;就算改完後,假如哪天又改成了Retrofit去實現網路操作,難道又去修改getUserMsg方法;這樣一連串的迭代,方法被改了多少遍,原來的程式碼不復存在,假如哪天需要用到以前的,那又得重新編寫,這不是遭罪嗎

可能有的朋友說,那就寫多個getUserMsg方法,面臨的問題就是一個類裡寫了太多這種同一種功能方法,繁雜,對以後的維護不是一個好訊息

所以這裡就採用面向介面的開發,不管以後的迭代中改成什麼需求;比如使用OKHttp請求,那就再定義一個類UserModelWithOkHttpImpl去實現上面的UserModel介面,因為介面是高度抽象的,只定義功能,具體實現因人而定;這樣操作以後,不僅保留了以前的程式碼,而且實現類簡潔易維護

編寫Controller

public class MainActivity extends AppCompatActivity implements View.OnClickListener, onUserListener {

    private String TAG = MainActivity.class.getSimpleName();

    private final int REQUEST_SUCCESS = 1;
    private final int REQUEST_ERROR = 2;

    private String userID = "12";

    private ProgressDialog dialog;
    private Button btn_getuser;
    private TextView tv_msg;

    private UserModel userModel;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        finView();
        initListener();
    }

    private void finView() {
        btn_getuser = (Button) findViewById(R.id.btn_getuser);
        tv_msg = (TextView) findViewById(R.id.tv_msg);
    }

    private void initListener() {
        btn_getuser.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {

        if (userModel == null) {
            userModel = new UserModelImpl();
        }
        userModel.getUserMsg(userID,this);
    }

    @Override
    public void onBefore() {
        if (dialog == null) {
            dialog = new ProgressDialog(this);
        }
        dialog.show();
    }

    /**
     * 這是是在子執行緒回撥,不能直接更新View
     * @param user
     */
    @Override
    public void onSuccess(User user) {
        Message message = handler.obtainMessage();
        message.obj = user;
        message.what = REQUEST_SUCCESS;
        handler.sendMessage(message);
    }

    /**
     * 這是是在子執行緒回撥,不能直接更新View
     * @param errorID
     */
    @Override
    public void onError(int errorID) {
        handler.sendEmptyMessage(REQUEST_ERROR);
    }

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int what = msg.what;
            switch (what) {
                case REQUEST_SUCCESS:
                    User user = (User) msg.obj;
                    tv_msg.append("姓名:"+user.getUserName()+"\n");
                    tv_msg.append("年齡:"+user.getAge()+"\n");
                    tv_msg.append("職業:"+user.getJob());
                    dialog.dismiss();
                    break;
                case REQUEST_ERROR:
                    tv_msg.setText("");
                    dialog.dismiss();
                    break;
            }
        }
    };

}

這裡邏輯挺簡單的

  • XML是屬於View層,對它的操作放在了Activity
  • 然而Activity作為Controller層,持有UserModel引用(UserModel 作為Model層),接著點選按鈕請求使用者資料,這樣Controller層就向Model層發起了請求
  • 然後Model層進行網路操作,因為可能耗時,所以在真正執行網路前通過回撥,通知View層顯示載入的dialog,然後獲取資料通過回撥返回給View層顯示出來

就這樣一個完整的閉環操作結束了

優點

  • 程式碼耦合性低,減小模組之間的相互影響;比如同一個數據模型,你可以用柱形圖來展示,也可以用圓形圖展示,也就是說修改View層的顯示效果不用修改資料
  • 可擴充套件性好,模組之間影響小,加上面向介面的開發,當你新增一個功能或者新增一種功能實現的時候,只需要定義介面和實現介面,就不需要修改以前的程式碼
  • 模組職責劃分明確,如上面所說,每個模組做自己的事
  • 程式碼維護性好,修改模型不會影響到檢視,反過來,修改檢視,也不會影響到模型

缺點

MVC其實也有自己的不足,要不然也不會有後來的MVP了

  • 增加了系統結構和實現的複雜性,對於簡單的介面,嚴格遵循MVC,使模型、檢視與控制器分離,會增加結構的複雜性,並可能產生過多的更新操作,降低執行效率
  • 檢視與控制器間的過於緊密的連線,也就是耦合性高,檢視沒有控制器的存在,其應用是很有限的,反之亦然,這樣就妨礙了他們的獨立重用

MVP

通過上面的例子可以看出來,在MVC中,XML檔案作為View層,能做的事實在是有限,比如你想在執行時改變下顏色,字型大小,或者背景啥的,幾乎沒辦法通過修改XML檔案去做到,可能有的人會說可以通過修改XML檔案位元組碼,那這就屬於槓精了;Activity作為Controller層,做了太多事,首先要載入應用佈局,然後初始化介面,View的操作,使用者邏輯基本上都在Activity開發

就這樣View層的相關操作幾乎都放到了Controller層去做,導致Activity的程式碼非常擁擠,並不是一個標準的MVC模型中的Controller;我之前接手的專案,一個Activity的程式碼兩三千行,邏輯混亂,不管開發還是維護那是巨痛苦的事;如果研究Android原始碼的同學可能就覺得一個類才兩千行程式碼不奇怪,要知道ViewRootImpl類的performTraversals方法都快一千行了,別說類了,RecyclerView都快上萬行了

而且MVC模型中還有一個比較重要的問題就是Model層和View層是可以直接交流的,這就導致兩者之間會存在耦合關係;還有Activity即屬於Controller,又屬於View,那這兩層也產生了耦合;對於一個成熟的專案來說,耦合是非常致命的,不管後期開發,維護還是測試都很難繼續下去

經過種種探索,MVP就應運而生了

MVP全稱Model-View-Presenter,它可以說是MVC的一種進化版本,解決了MVC的不少痛點,不過還是有一定的相似性

劃分

  • Model:這裡跟MVC中的Model基本一樣
  • View:這裡就跟MVC中的View不太一樣了,不僅僅是XML檔案,還包括Activity,Fragment,自定義View等java檔案,這樣Activity等就可以只負責View的繪製,顯示,響應使用者的操作
  • Presenter:這就是重點了,作為View與Model之間聯絡的紐帶,讓View和Model解耦;同時它與View層是通過介面通訊,這又與View層解耦;將原來Activity屬於Controller的操作移到了Presenter中

與MVC區別

  • MVP中View與Model並不直接互動,而是通過與Presenter互動來與Model間接互動。而在MVC中View可以與Model直接互動
  • 通常View與Presenter是一對一的,但複雜的View可能繫結多個Presenter來處理邏輯。而Controller是基於行為的,並且可以被多個View共享,Controller可以負責決定顯示哪個View
  • Presenter與View的互動是通過介面來進行的,更有利於新增單元測試;而MVC中Activity包含了View和Controller程式碼,很難做測試

在Android中的理解

在Android中的MVP,XML,Activity,Fragment等都是屬於View層,不在執行具體的邏輯,只是純粹的操作View;具體邏輯交給Presenter處理,View與Presenter通過介面通訊,達到解耦;Presenter收到View層請求後轉發給相應的Model層,Model層處理完資料後將資料返回給Presenter,然後Presenter再通知View層更新View

樣例

現在以一個使用者登入的需求為例進行講解

注意,MVP的核心思想就是一切皆為介面,把Activity中的UI邏輯抽象成View介面,把業務邏輯抽象成Presenter介面,把資料操作抽象成Model介面

編寫View層

首先是xml,很簡單 兩個輸入框 一個按鈕,一個提示的textview

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/et_name"
        android:layout_centerHorizontal="true"
        android:layout_width="@dimen/length_200"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_20"
        android:hint="@string/hint_name"
        android:textSize="@dimen/size_14"/>

    <EditText
        android:id="@+id/et_pw"
        android:layout_below="@+id/et_name"
        android:layout_centerHorizontal="true"
        android:layout_width="@dimen/length_200"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_20"
        android:hint="@string/hint_pw"
        android:textSize="@dimen/size_14"/>

    <Button
        android:id="@+id/btn_login"
        android:layout_below="@+id/et_pw"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/margin_20"
        android:text="@string/login"
        android:textSize="@dimen/size_14"
        android:textColor="@android:color/white"
        android:background="@color/colorAccent"/>

    <TextView
        android:id="@+id/tv_hint"
        android:layout_below="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_20"
        android:gravity="center"
        android:textSize="@dimen/size_14"/>

</RelativeLayout>

用到的資原始檔如下

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="length_200">200dp</dimen>
    <dimen name="margin_20">20dp</dimen>
    <dimen name="size_14">14dp</dimen>
</resources>
<resources>

    <string name="hint_name">請輸入使用者名稱</string>
    <string name="hint_pw">請輸入密碼</string>
    <string name="login">登 錄</string>

    <string name="login_success">登入成功</string>
    <string name="login_fail">登入失敗</string>

    <string name="username_isempty">使用者名稱不能為空</string>
    <string name="userpw_isempty">密碼不能為空</string>
</resources>
/**
 * @Description TODO(由View層實現,主要是響應presenter層的操作)
 * @author cxy
 * @Date 2018/10/25 17:15
 */
public interface ILoginView  extends OnProgressListener {

    /**
     * @Description TODO(將View中資料反饋給presenter)
     * @author cxy
     */
    User getUserMsg();

    /**
     * @Description TODO(清除edittext資料)
     * @author cxy
     */
    void clearEdit();

    /**
     * @Description TODO(將登入資料返回給View)
     * @author cxy
     * @parame result 登入返回資料
     */
    void setLoginResult(String result);

}

這就是把UI邏輯抽象成了介面,由Activity去實現

/**
 * @Description TODO(顯示進度條 隱藏進度的介面,統一定義,避免每個需要進度顯示的介面重複定義這些方法)
 * @author cxy
 * @Date 2018/10/25 17:07
 */
public interface OnProgressListener {

    void onShowProgress();
    void onHideProgress();
}

還有Activity的程式碼最後放出來,現在放出來有點突然

編寫Model層

先定義介面,Model層既然要處理網路操作,那肯定要有網路請求方法,先定義出來,具體誰實現,怎麼實現,那是實現的人的事

/**
 * @Description TODO(由相應的model實現,關於使用者資料操作介面)
 * @author cxy
 * @Date 2018/10/25 16:55
 */
public interface IUserModel {

    /**
     * @Description TODO(提供網路請求操作)
     * @author cxy
     * @parame url 網路請求連結
     * @parame resultListener 請求回撥介面
     */
    void onHttpRequest(String url, OnResultListener resultListener);
}

然後需要去實現

/**
 * @Description TODO(網路請求具體實現類)
 * @author cxy
 * @Date 2018/10/25 17:02
 */
public class UserModelImpl implements IUserModel{

    /**
     * @Description TODO(實現具體的登入邏輯)
     * @author cxy
     * @Date 2018/10/25 17:09
     */
    @Override
    public void onHttpRequest(final String url, final OnResultListener listener) {

        if (listener == null) throw new NullPointerException("OnResultListener is null,this is illegal");
        if (TextUtils.isEmpty(url)) throw new NullPointerException("url is null,this is illegal");

        //這裡模擬網路耗時操作,實際開發中絕對不要這樣直接new Thread
        new Thread(new Runnable() {
            @Override
            public void run() {

                String result = "";

                //接下來就是網路操作 進行登入 獲取登入結果
                try {
                    Thread.sleep(2000);
                    result = "success";
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if (TextUtils.isEmpty(result)) {
                    listener.onError();
                } else {
                    listener.onSuccess();
                }

            }
        }).start();

    }

}

編寫Presenter層

也是先定義業務邏輯介面,既然登入操作,那就定義就完事了

/**
 * @Description TODO(由相應presenter實現,登入操作對應的presenter,連線View和model)
 * @author cxy
 * @Date 2018/10/25 17:03
 */
public interface ILoginPresenter {

    /**
     * @Description TODO(登入中轉點,由View層呼叫,在該方法中將請求轉給model)
     * @author cxy
     * @Date 2018/10/25 17:12
     */
    void onLogin();
}

由相應的presenter實現

/**
 * @Description TODO(登入presenter對應的具體實現類)
 * @author cxy
 * @Date 2018/10/25 17:12
 */
public class LoginPresenter implements ILoginPresenter, OnResultListener {

    private final int REQUEST_SUCCESS = 1;
    private final int REQUEST_ERROR = 2;

    private ILoginView loginView;
    private IUserModel userModel;

    private WeakReference<Context> mContext ;

    public LoginPresenter(Context context,ILoginView loginView) {
        this.loginView = loginView;
        userModel = new UserModelImpl();
        mContext = new WeakReference<>(context);
    }

    @Override
    public void onLogin() {
        loginView.onShowProgress();

        User user = loginView.getUserMsg();
        if (StringUtils.isEmpty(user.getUserName(),user.getPw()) ) {
            handler.sendEmptyMessage(REQUEST_ERROR);
            return;
        }

        String url ;

        //接下來進行登入請求連結的組裝
        url = "www.baidu.com";

        userModel.onHttpRequest(url,this);
    }

    @Override
    public void onSuccess() {
        handler.sendEmptyMessage(REQUEST_SUCCESS);
    }

    @Override
    public void onError() {
        handler.sendEmptyMessage(REQUEST_ERROR);
    }

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int what = msg.what;
            switch (what) {
                case REQUEST_SUCCESS:
                    loginView.onHideProgress();
                    loginView.setLoginResult(mContext.get().getResources().getString(R.string.login_success));
                    break;
                case REQUEST_ERROR:
                    loginView.onHideProgress();
                    loginView.clearEdit();
                    loginView.setLoginResult(mContext.get().getResources().getString(R.string.login_fail));
                    break;
            }
        }
    };
}
/**
 * @Description TODO(操作結果回撥)
 * @author cxy
 * @Date 2018/10/25 18:38
 */
public interface OnResultListener {

    void onSuccess();

    void onError();

}

可以看到這裡持有Model層和View層介面的引用,通過介面獲取View層相關資料及控制View的變化,然後通過介面去呼叫Model層相關方法,最後又通過View層介面將結果返回

還有View層最後一個Activity

public class MainActivity extends AppCompatActivity implements View.OnClickListener, ILoginView {

    private EditText et_name;
    private EditText et_pw;
    private Button btn_login;
    private TextView tv_hint;

    private ProgressDialog dialog;
    private ILoginPresenter loginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findView();
        initListener();
    }

    private void findView() {
        et_name = (EditText) findViewById(R.id.et_name);
        et_pw = (EditText) findViewById(R.id.et_pw);
        btn_login = (Button) findViewById(R.id.btn_login);
        tv_hint = (TextView) findViewById(R.id.tv_hint);
    }

    private void initListener() {
        btn_login.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {

        if (loginPresenter == null) {
            loginPresenter = new LoginPresenter(this,this);
        }
        if (dialog == null) {
            dialog = new ProgressDialog(this);
        }

        if (TextUtils.isEmpty(et_name.getText().toString())) {
            T.show(this, getResources().getString(R.string.username_isempty));
            return;
        }

        if (TextUtils.isEmpty(et_pw.getText().toString())) {
            T.show(this, getResources().getString(R.string.userpw_isempty));
            return;
        }

        loginPresenter.onLogin();
    }

    @Override
    public void onShowProgress() {
        dialog.show();
    }

    @Override
    public void onHideProgress() {
        dialog.dismiss();
    }

    @Override
    public User getUserMsg() {

        User user = new User();
        user.setUserName(et_name.getText().toString());
        user.setPw(et_pw.getText().toString());
        return user;
    }

    @Override
    public void clearEdit() {
        et_name.setText("");
        et_pw.setText("");
    }

    @Override
    public void setLoginResult(String result) {
        tv_hint.setText(result);
    }


}

可以看到,在MainActivity中,只是純粹的做一些View相關的工作,沒有任何業務邏輯,所有使用者操作全部轉交給presenter去幹,這裡只是使用者點選登入按鈕這個業務邏輯,實際上,使用者的觸屏操作多了去了,比如點選按鈕重新整理,刪除跳轉到另外一個頁面等,這些所有邏輯都可以在presenter介面中定義相應的方法,然後Activity中去呼叫就行了

公共類

/**
 * @Description TODO(字串操作公用類)
 * @author cxy
 * @Date 2018/10/25 19:02
 */
public class StringUtils {

    /**
     * @Description TODO(判斷字串是否為empty)
     * @author cxy
     * @return true 有一個為empty就為true , false 都不為empty就為false
     * @parame 接收一個字串陣列
     */
    public static boolean isEmpty(String... values){

        boolean isEmpty = false;

        for(int i=0; i<values.length; i++){
            if (TextUtils.isEmpty(values[i])) {
                isEmpty = true;
                break;
            }
        }

        return isEmpty;
    }
}
/**
 * @Description TODO(提示公用類)
 * @author cxy
 * @Date 2018/10/25 19:10
 */
public class T {

    public static void show(Context context,String hint){
        Toast.makeText(context,hint,Toast.LENGTH_SHORT).show();
    }

}

通過介面操作後,可以發現View,Model,Presenter三者可以單獨工作

沒了View,Presenter依然可以工作,在單元測試的時候,比如想測試Presenter中的業務邏輯是否正確,只需要寫個指令碼模擬Activity,然後在相應的方法中提供資料,最後呼叫Presenter介面的login方法,看能否得到預期結果,而且兩者完成解耦;而且Presenter只專注於業務邏輯,至於頁面是什麼樣的,不關心,所以同一個Presenter可以對應多個View;

去掉Presenter,Model依然可以獨立工作,只需要給指令碼實現回撥介面,然後呼叫Model介面,傳遞引數,呼叫相應的資料操作方法就可以測試Model是否能正常工作,也能實現解耦

用一張圖表示MVP

在這裡插入圖片描述

優點

  • 幾乎所有的思想都是為了解耦,更加利於維護和開發,把大工程化整為零,每個團隊負責一小部分,相互獨立
  • 從上面的樣例也可以看出,這種模式下,Activity等View層類的程式碼相當簡潔,基本上只有對View的操作;還有想了解一個模組有哪些業務,就看這個模組的Presenter介面就行了,不管是定位程式碼還是後續修改業務都很方便
  • 由於面向介面的開發,非常便於單元測試
  • 程式碼複用程度提高,一個Presenter可以適用於多個View

缺點

  • 從上面的例子明顯可以看出來,適用MVP後,類的數量能增加一倍以上,也給維護工作增加了難度,這應該是最大的一個缺點了
  • 維護介面的難度並不低,特別是業務邏輯越來越複雜以後,維護工作更難
  • Presenter有很多連線View與Model之間的操作,造成Presenter的臃腫
  • Presenter和View通過介面通訊太繁瑣,一旦View層需要的資料變化,那麼對應的介面就需要更改

總結

不管是MVC還是MVP都有自己的優點和缺點,沒有哪個專案是絕對的適合MVC或者適合MVP,有時候你的專案可能是兩者相結合的情況,並且對於使用MVP帶來的類數量暴增的情況,需要想辦法緩解;可以通過Template自動生成需要的類和介面,這樣少去了頻繁的複製貼上;對於一些簡單邏輯的頁面,可以不使用MVP,直接用MVC;如果有一些頁面業務邏輯差不多,可以複用一些Presenter;還可以通過封裝基本類,將一些公用方法提取到父類,避免每個介面都去定義這些;或者使用一些第三方框架,比如像支付寶團隊使用的TheMVP框架,是通過將Activity或Fragment作為Presenter,將UI操作抽到Delegate中,作為View層;還有一個是通過第三方的訊息訂閱釋出框架或者Handler進行訊息通訊,代替過多的介面通訊,這也能減少不少的類;具體怎麼合適,需要自己在實踐中多嘗試

對於新手來說,剛做專案從零開始,就不要想太多設計架構的問題了,先做出來才是王道,後續再考慮重構的問題,因為所有的優化都是基於你對這些現有模組都很熟悉的情況,如果一開始就想這些,可是都不知道要做成什麼樣,那什麼時候才能寫完專案