MVX調研有話說

小序

隨着開發框架的潮流影響,再加上組織上給予的壓力。看來不得不邁出MVC君的守望之門了,彷彿一股熱流向此處湧來,其實際上已然有好多前輩寫下了大寫的文章在等着我。此刻起只要好好了解一番,再弄一兩個小Demo便可造就一片新的天地了,然後就可以在藍天白雲下沐浴在陽光下奔跑了,光想想還是有點小激動的。


MVC

1.1.介紹

Created with Raphaël 2.1.0ViewViewControllerControllerModelModelUser嘗試點擊觸發點擊事件發起數據請求處理數據完畢,反饋更新User看到效果

1.2.還是介紹

以上介紹主要是繪製並介紹了MVC層與層之間的交互過程,下面具體整理下各個層的含義與作用域:

who what where
Model 業務邏輯處理層 數據源處理,網絡加載,複雜算法
View 界面顯示層 佈局文件.xml
Controller 控制器 Activity、Fragment

1.3.潛在輸出

  • 將內容顯示以及業務邏輯處理分隔開來,模塊職責劃分明確,有利於代碼維護;
  • 提升項目的可擴展性與維護性,並降低耦合度;
  • 對業務邏輯複雜且頁面較多的項目更能提升MVC的優勢;

1.4.承受傷害

  • View與Controller融合在一起。大多數人通過Activity/Fragment直接控制View內容的更新;
  • View完全依賴Model。忽略Controller的控制直接從Model獲取數據;
  • View自己承擔部分業務邏輯。Model失去可重用的業務邏輯處理;
  • Controller同時負責View與Model的任務處理。使得代碼異常臃腫,顯得較爲混亂;

1.5.代碼飄過

/**
 8. Controller
 */
public class MainActivity extends AppCompatActivity {

    // 文本
    private TextView mMainTv;
    // 按鈕
    private Button mMainBtn;

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

    /**
     * View
     */
    private void initView() {
        this.mMainTv = (TextView) findViewById(R.id.main_tv);
        this.mMainBtn = (Button) findViewById(R.id.main_btn);

        mMainBtn.setOnClickListener(v -> {
                mMainTv.setText(getDateTimeStr());
        });
    }

    /**
     * Model
     *
     * @return 當前日期時間
     */
    private String getDateTimeStr() {
        Date curDate = new Date(System.currentTimeMillis());
        return SimpleDateFormat.getDateTimeInstance().format(curDate);
    }
}

以上代碼只是簡單實現按鈕點擊獲取當前的時間,並賦值給文本顯示。其主要是表現大多數停留在MVC的夥伴,都會以這樣一個方式去創建模塊的頁面和功能。至少接觸過的很多項目裏是這樣的,當然並沒有反對的意思,因爲只要合理分配也是極好的。這裏更多的是爲了鑑於這個類來比較:

  1. 清晰mvc再戰android

MVP

2.1.介紹

Created with Raphaël 2.1.0ViewViewPresenterPresenterModelModelUser嘗試點擊觸發點擊事件發起數據請求處理數據完畢反饋更新User看到效果

2.2.依然是介紹

同理,以上介紹主要是繪製並介紹了MVP層與層之間的交互過程。其情節內容沒有改動,因爲一個功能的思想一樣不受使用模式的影響。下面具體整理下各個層的含義與作用域:

who what where
Model 數據處理層 數據的檢索,存儲等操作
View 界面顯示層 Activity、Fragment、佈局文件.xml
Presenter 層現器 作爲View與Model兩者的中間樞紐,處理與用戶交互的負責邏輯

2.3.潛在輸出

  • View層與Model層完全分離,兩者間的修改互不影響;
  • Presenter控制View與Model之間的交互,大大提升模型的使用效率;
  • Presenter可以被多個View綁定(MVC當中Controller服務多個View);
  • 可完全脫離用戶接口實現單元測試;
  • 解決MVC當中Activity代碼臃腫的問題;

2.4.承受傷害

  • 由於Presenter可以被多個View綁定,且如果與Presenter之間的交互現象過於頻繁,一旦View需要變更,那麼Presenter也需要變更從而影響Presenter的複用性;
  • 大量的View與Model的手動同步邏輯,造成Presenter比較笨重,維護起來會比較困難;

2.5.前期裝備

啥?還有前期裝備,這是要逆天嘛,還有這裝備到底是什麼。請恕我直言,如果前期沒有輔助那必須喫力些呀,所以View有理由學會擺脫與Presenter的直接交互,繼而通過View interface來配合View接受Presenter返回結果的處理。下面是前期裝備屬性:

  • 降低View與Presenter之間耦合度;
  • 方便進行單元測試,可繞過View直接定義類實現interface模擬View與Presenter之間的交互;

2.6.後期裝備

啥?還有後期,這發育的也太狠了吧。請恕我直言,這裝備我也還沒用過。當然它的屬性是很誘人的,滿滿的被動輸出傷害有木有。其實際上就是前輩們挖掘出來的兩種View模式,用於劃分View與Presenter的任務內容,具體如下:

who what
PV(Passive View) View的UI元素委託給Presenter操作
SoC(Supervising Controller) View自己負責UI處理以及數據綁定

2.7.代碼飄過

相信剛接觸MVP模式也許會像我一樣,摸不清構建順序吧。因爲項目比較緊急,且又急於嘗試MVP的實現,我僅在項目裏使用了MVP+XUtils3的框架。下面貼出的代碼僅供參考。

  • 基礎構建
public class BasePresenter<V> {

    public V mvpView;
    public Context mContext;

    public BasePresenter(Context context, V mvpView) {
        this.mContext = context;
        this.mvpView = mvpView;
    }

    public void detachView() {
        this.mvpView = null;
    }
}
public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity {

    public P mvpPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mvpPresenter = createPresenter();
    }

    protected abstract P createPresenter();
}
public abstract class BaseFragment<P extends BasePresenter> extends Fragement {

    public P mvpPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mvpPresenter = createPresenter();
    }

    protected abstract P createPresenter();
}
  • 具體實現,這裏以請求登錄操作爲例。View interfacePresenterActivity/Fragment(感覺順些)
public interface LoginView {

    void userLoginSuccess(UserInfo userInfo);

    void userLoginFail(String failMsg);
}
public class LoginPresenter extends BasePresenter<LoginView> {

    public void loginUser(String account, String pwd) {
        UserLogin userLogin = new UserLogin();
        userLogin.Account = account;
        userLogin.Password = pwd;

        RequestParams params = new RequestParams(ApiConfig.USER_IP + ApiConfig.USER_LOGIN);
        params.setBodyContent(userLogin.toJSONString());

        // 請求結果需對mvpView判空處理
        x.http().post(params, new Callback.CommonCallback<UserInfo>() {

            @Override
            public void onSuccess(UserInfo model) {
                if (null == mvpView) return;
                if (model.getStatusCode() == 200) {
                    mvpView.userLoginSuccess(model.getData());
                } else {
                    mvpView.userLoginFail(model.getMessage());
                }
            }

            @Override
            public void onError(Throwable ex, boolean isOnCallback) {
                if (null == mvpView) return;
                mvpView.userLoginFail("請求出錯/網絡異常");
            }

            @Override
            public void onCancelled(CancelledException cex) {}

            @Override
            public void onFinished() {}
        });
    }
}
@ContentView(R.layout.activity_login)
public class LoginActivity extends BaseActivity<LoginPresenter> implements LoginView {

    @ViewInject(R.id.login_edt_id)
    private EditText mIdEdt;

    @ViewInject(R.id.login_edt_pwd)
    private EditText mPwdEdt;

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);
    }

    @Event(value = {R.id.login_btn}, type = View.OnClickListener.class)
    private void onAppClick(View view) {
        String idStr = mIdEdt.getText().toString();
        String pwdStr = mPwdEdt.getText().toString();
        // 爲了代碼簡潔些,判空等處理被捨棄了
        mvpPresenter.loginUser(idStr, pwdStr);
    }

    @Override
    public void userLoginSuccess(UserInfo userInfo) {
        // 登錄成功,執行相應操作
    }

    @Override
    public void userLoginFail(String failMsg) {
        // 登錄失敗,Toast提示錯誤信息
    }
}

以上只是個人實踐內容,其實際上還有很多與MVP結合使用的大寫之作,推薦:

  1. MVP+Retrofit+RxJava
  2. MVP+Dagger2+RxJava+Retrofit2

MVVM

3.1.介紹

Created with Raphaël 2.1.0ViewViewViewModelViewModelModelModelUser嘗試點擊數據源綁定更新,觸發點擊事件發起數據請求,更新數據源User看到效果

3.2.當然還是介紹

以上繪製並介紹了MVVM層與層之間的交互過程,其左邊描述View與數據源的綁定,即控件顯示數據與綁定的對象數據同步更新(可以忽略時序圖的前後順序這一說,只怪提供的繪圖方法有限)。下面具體整理下各個層的含義與作用域:

who what where
Model 數據處理層 數據的檢索,存儲等操作
View 界面顯示層 Activity、Fragment、佈局文件.xml
ViewModel 視圖模型 可理解爲View的Model和Presenter之間的融合

3.3.潛在輸出

  • 數據捆綁UI更新。數據與業務邏輯處在一個獨立的ViewModel當中,由數據主導UI的更新(eg. DataBinding框架實現被捆綁的UI更新)
  • 低耦合度。ViewModel不對View的任何控件保持持有狀態,更改UI無需修改ViewModel的實現;
  • 團隊協作。扮演UI處理以及數據和業務邏輯的兩個不同角色;
  • 複用性強。同一個ViewModel可以爲多個View服務;
  • 單元測試。只需要在ViewModel執行單元測試,完全不需要依賴View的任何操作;

3.4.承受傷害

  • 多個View與ViewModel執行捆綁,造成ViewModel比較笨重;
  • 數據對象的直接引用使得後期維護起來會比較困難;

3.5.這裏木有代碼

只是瞭解了下但還沒有確切實踐過MVVM,暫時也就沒有代碼飄過了


總結

4.1.共同點

who what
View 一直扮演者與用戶交互的角色
Model 因數據處理而忙碌着

4.2.不同點

who what
Controller 被動的負責將View的需求告知Model處理並讓Model通知View更新
Presenter 在Model前面拿下View的請求做一定的業務處理纔給回Model做數據操作處理,
數據請求處理結束後再將Model數據返回給View並提示更新
ViewModel 在Presenter的基礎上綁定並實現了對View的部分數據操作


PS1.沒有什麼絕對好的開發模式,倘若沒有規範好自身的代碼,一切都將是空談
PS2.以上內容若存在不足之處,還望大蝦們指點一二

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章