MVP架構在Android中的實踐

爲什麼要重視程序的架構設計

對程序進行架構設計的原因,歸根結底是爲了提高生產力。通過設計是程序模塊化,做到模塊內部的高聚合和模塊之間的低耦合(如依賴注入就是低耦合的集中體現)。

這樣做的好處是使得程序開發過程中,開發人員主需要專注於一點,提高程序開發的效率,並且更容易進行後續的測試以及定位問題。

但是,設計不能違背目的,對於不同量級的工程,具體的架構實現方式必然不同,不要爲了設計而設計,爲了架構而架構。比如一個Android app如果只有幾個Java文件,那只需要做點模塊和層次的劃分就可以了。引入框架或者架構增加了工作量,降低了生產力。

所以在開發的時候需要考慮:

1)當前這個項目是否需要以最快速度上線。比如有些創業公司,爭取的就是時間,公司老闆是要拿着這個app是去找投資的。

2)如果這個項目開發週期還可以,第一個版本就可以把app架構做好。因爲一個App肯定是朝着慢慢做大的方向去的,如果等業務到了一定程度了,再去重構的話,成本就有點大。

什麼是MVP?

MVP架構由MVC發展而來。在MVP中,M代表Model,V代表View,P代表Presenter。

Model 負責獲取數據,數據的來源可以是網絡或本地數據庫等;

View 負責界面數據的展示,與用戶進行交互;

Presenter 是Model與View之間的通信的橋樑,將Model與View分離開來。

MVP架構圖:

對程序進行架構設計的原因,歸根結底是爲了提高生產力。通過設計是程序模塊化,做到模塊內部的高聚合和模塊之間的低耦合(如依賴注入就是低耦合的集中體現)。

這樣做的好處是使得程序開發過程中,開發人員主需要專注於一點,提高程序開發的效率,並且更容易進行後續的測試以及定位問題。

但是,設計不能違背目的,對於不同量級的工程,具體的架構實現方式必然不同,不要爲了設計而設計,爲了架構而架構。比如一個Android app如果只有幾個Java文件,那只需要做點模塊和層次的劃分就可以了。引入框架或者架構增加了工作量,降低了生產力。

所以在開發的時候需要考慮:

1)當前這個項目是否需要以最快速度上線。比如有些創業公司,爭取的就是時間,公司老闆是要拿着這個app是去找投資的。

2)如果這個項目開發週期還可以,第一個版本就可以把app架構做好。因爲一個App肯定是朝着慢慢做大的方向去的,如果等業務到了一定程度了,再去重構的話,成本就有點大。

什麼是MVP?

MVP架構由MVC發展而來。在MVP中,M代表Model,V代表View,P代表Presenter。

Model 負責獲取數據,數據的來源可以是網絡或本地數據庫等;

View 負責界面數據的展示,與用戶進行交互;

Presenter 是Model與View之間的通信的橋樑,將Model與View分離開來。

MVP架構圖:
這裏寫圖片描述
所以MVP的架構有如下好處:

1)降低了View和Model的耦合,通過Presenter層來通信;

2)把視圖層抽象到View接口,邏輯層抽象到Presenter接口,提高了代碼的可讀性、可維護性;

3)Activity和Fragment功能變得更加單一,只需要處理View相關的邏輯;

4)Presenter抽象成接口,就可以有多種實現,方便單元測試。

下面就來實際的體驗一下MVP在項目中的使用吧!(用戶註冊和文章詳情兩個例子)

用戶註冊

功能界面如下圖所示(註冊、登錄):
這裏寫圖片描述
下面是完整的代碼,主要實現了驗證碼註冊、登錄的功能,可能代碼比較多:

Model部分:

public class UserEngine extends BaseEngine {

    public static final int ID_LOGIN = 1;
    public static final int ID_REGISTER = 2;

    private UserApi userApi;

    private UserEngine(Callback callback, int... ids) {
        super(callback, ids);
        userApi = ApiFactory.createService(UserApi.class, true);
    }

    public static UserEngine getInstance(Callback callback, int... ids) {
        return new UserEngine(callback, ids);
    }

    /**
     * 用戶登錄
     *
     * @param phone    手機號碼
     * @param password 密碼
     */
    public void login(String phone, String password) {
        //請求服務器
        //成功失敗,通過回調通知
    }

    /**
     * 用戶註冊
     */
    public void register(String phone, String password) {
        //請求服務器
        //成功失敗,通過回調通知
    }
}

public interface BaseView {

    void showToast(String message);

    void showLoading();

    void hideLoading();

}

public interface ILoginView extends BaseView {

    /**
     * 切換 登錄/註冊 界面
     */
    void switchUiByActionType(int actionType);

    /**
     * 用戶名錯誤
     *
     * @param errorMsg 錯誤消息
     */
    void setUsernameError(String errorMsg);

    /**
     * 驗證碼錯誤
     */
    void setCodeError(String errorMsg);

    /**
     * 密碼錯誤
     *
     * @param errorMsg 錯誤消息
     */
    void setPasswordError(String errorMsg);

    /**
     * 登錄成功
     */
    void loginSuccess();

    /**
     * 校驗驗證碼成功
     *
     * @param data 成功數據
     */
    void verifySmsCodeSuccess(Object data);

    /**
     * 發送驗證碼成功
     *
     * @param data 成功數據
     */
    void sendSmsCodeSuccess(Object data);

    /**
     * 獲取手機號支持的國家
     *
     * @param data 成功數據
     */
    void getSupportCountrySuccess(Object data);

}

//Activity 實現IView接口
public class UserLoginActivity extends BaseActivity<UserLoginBinding> implements ILoginView
        , MyCountDownTimer.CountDownCallback {


    private static final int ACTION_TYPE_LOGIN = 1;
    private static final int ACTION_TYPE_REGISTER = 2;


    private LoginPresenterImpl loginPresenter;

    private MyCountDownTimer countDownTimer;

    private int actionType;

    public static void launchForLogin(Context context) {
        Intent intent = new Intent(context, UserLoginActivity.class);
        intent.putExtra("actionType", ACTION_TYPE_LOGIN);
        context.startActivity(intent);
    }


    public static void launchForRegister(Context context) {
        Intent intent = new Intent(context, UserLoginActivity.class);
        intent.putExtra("actionType", ACTION_TYPE_REGISTER);
        context.startActivity(intent);
    }

    @Override
    protected void initParams() {
        super.initParams();
        //默認爲登錄界面
        actionType = getIntent().getIntExtra("actionType", ACTION_TYPE_LOGIN);
        loginPresenter = new LoginPresenterImpl(this);
        countDownTimer = new MyCountDownTimer(60000, 1000, this);
    }

    @Override
    protected void initViews() {
        super.initViews();
        setTitle("登錄");

        binding.btnLoginRegister.setOnClickListener(this);
        binding.tvGetCode.setOnClickListener(this);
        binding.tvToggleUi.setOnClickListener(this);

        loginPresenter.initSMSSDK(this);
        loginPresenter.switchUiByActionType(actionType);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_user_login_layout;
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void onClick(View view) {
        super.onClick(view);
        switch (view.getId()) {
            case R.id.tv_get_code:
                loginPresenter.sendVerificationCode(binding.etUsername.getText().toString());
                break;
            case R.id.btn_login_register:
                if (actionType == ACTION_TYPE_LOGIN) {
                    loginPresenter.login(xxx);
                } else {
                    loginPresenter.submitVerificationCode(xxx);
                }
                break;
            case R.id.tv_toggle_ui:
                if (VersionUtils.hasKITKAT()) {
                    TransitionManager.beginDelayedTransition(binding.llContainer);
                }
                loginPresenter.switchUiByActionType(
                        actionType == ACTION_TYPE_LOGIN ? ACTION_TYPE_REGISTER : ACTION_TYPE_LOGIN);
                break;
        }
    }

    @Override
    public void showToast(String message) {
        ToastUtils.showShortToast(this, message);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (countDownTimer != null) {
            countDownTimer.cancel();
        }
        loginPresenter.destroy();
    }

    @Override
    public void switchUiByActionType(int actionType) {
        this.actionType = actionType;
        if (actionType == ACTION_TYPE_REGISTER) {
            setTitle("註冊");
            binding.tvToggleUi.setText(R.string.flag_login);
            binding.rlPhoneCode.setVisibility(View.VISIBLE);
            binding.btnLoginRegister.setText(R.string.btn_register);
        } else {
            setTitle("登錄");
            binding.tvToggleUi.setText(R.string.flag_register);
            binding.rlPhoneCode.setVisibility(View.GONE);
            binding.btnLoginRegister.setText(R.string.btn_login);
        }
    }

    @Override
    public void showLoading() {
        showLoadingDialog();
    }

    @Override
    public void hideLoading() {
        hideLoadingDialog();
    }

    @Override
    public void setUsernameError(String errorMsg) {
        binding.etUsername.requestFocus();
        binding.tilUsername.setError(errorMsg);

    }

    @Override
    public void setCodeError(String errorMsg) {
        binding.etCode.requestFocus();
        binding.tilPhoneCode.setError(errorMsg);
    }

    @Override
    public void setPasswordError(String errorMsg) {
        binding.etPassword.requestFocus();
        binding.tilPassword.setError(errorMsg);
    }

    @Override
    public void loginSuccess() {
        finish();
    }


    @Override
    public void verifySmsCodeSuccess(Object data) {
        loginPresenter.sendVerificationCode(binding.etCode.getText().toString());
    }

    @Override
    public void sendSmsCodeSuccess(Object data) {
        binding.tvGetCode.setClickable(false);
        countDownTimer.start();
    }

    @Override
    public void getSupportCountrySuccess(Object data) {
        //返回支持發送驗證碼的國家列表
        Log.d("SMS", data.toString());
        ArrayList<HashMap<String, String>> cs = (ArrayList) data;
        for (HashMap<String, String> map : cs) {
            Log.d("SMS", map.toString());
            for (Map.Entry<String, String> entry : map.entrySet()) {
                System.out.println(entry.getKey() + "-" + entry.getValue());
            }
        }
    }

    @Override
    public void onTimerTick(long millisUntilFinished) {
        binding.tvGetCode.setText(String.format(getString(R.string.count_down_timer)
                , millisUntilFinished / 1000L));
    }

    @Override
    public void onTimerFinish() {
        binding.tvGetCode.setClickable(true);
        binding.tvGetCode.setText(R.string.get_varification_code);
    }

}

Presenter

public interface BasePresenter {
    void destroy();
}

public interface ILoginPresenter extends BasePresenter{

    void switchUiByActionType(int actionType);

    void onUsernameError(String errorMsg);

    void onPasswordError(String errorMsg);

    void onCodeError(String errorMsg);

    void submitVerificationCode(String phone, String code, String password);

    void sendVerificationCode(String phone);

    void login(String username, String password);

    void register(String username, String password, String code);

    void loginSuccess();

    void registerSuccess();

    void loginFailed(String errorMsg);

    void registerFailed(String errorMsg);

    void verifySmsCodeSuccess(Object data);
    void sendSmsCodeSuccess(Object data);
    void getSupportCountrySuccess(Object data);

    void smsFailed(int event, Object data);

    void initSMSSDK(Context context);
}

public class LoginPresenterImpl implements ILoginPresenter,
        BaseEngine.Callback, SMSCallback {

    private UserEngine userEngine;//相當於Model

    private ILoginView loginView;

    private SMSEventHandler smsEventHandler;

    public LoginPresenterImpl(ILoginView loginView) {
        this.loginView = loginView;
        userEngine = UserEngine.getInstance(this, UserEngine.ID_LOGIN, UserEngine.ID_REGISTER);
    }

    @Override
    public void initSMSSDK(Context context) {
        SMSSDK.initSDK(context, "15da6511b04f5", "ec275402ed1402d13d37132c55ae90c0");
        smsEventHandler = new SMSEventHandler(this);
        //註冊短信回調
        SMSSDK.registerEventHandler(smsEventHandler);
    }

    public void switchUiByActionType(int actionType) {
        if (loginView != null) {
            loginView.switchUiByActionType(actionType);
        }
    }

    @Override
    public void onUsernameError(String errorMsg) {
        if (loginView != null) {
            loginView.setUsernameError(errorMsg);
        }
    }

    @Override
    public void onCodeError(String errorMsg) {
        if (loginView != null) {
            loginView.setCodeError(errorMsg);
        }
    }

    @Override
    public void onPasswordError(String errorMsg) {
        if (loginView != null) {
            loginView.setPasswordError(errorMsg);
        }
    }

    @Override
    public void sendVerificationCode(String phone) {
        String error;
        if ((error = checkPhone(phone)) != null) {
            onUsernameError(error);
            return;
        }

        if (loginView != null) {
            loginView.showLoading();
        }
        SMSSDK.getVerificationCode("86", phone.trim());
    }

    @Override
    public void submitVerificationCode(String phone, String code, String password) {
        String error;
        if ((error = checkPhone(phone)) != null) {
            onUsernameError(error);
            return;
        } else if ((error = checkCode(code)) != null) {
            onCodeError(error);
            return;
        } else if ((error = checkPassword(password)) != null) {
            onPasswordError(error);
            return;
        }

        SMSSDK.submitVerificationCode("86", phone.trim(), code.trim());
    }

    @Override
    public void register(String phone, String password, String code) {
        String error;
        if ((error = checkPhone(phone)) != null) {
            onUsernameError(error);
            return;
        } else if ((error = checkCode(code)) != null) {
            onCodeError(error);
            return;
        } else if ((error = checkPassword(password)) != null) {
            onPasswordError(error);
            return;
        }
        if (loginView != null) {
            loginView.showLoading();
        }
        if (userEngine != null) {
            userEngine.register(phone, password);
        }
    }


    @Override
    public void login(String phone, String password) {

        String error;
        if ((error = checkPhone(phone)) != null) {
            onUsernameError(error);
            return;
        }
        if ((error = checkPassword(password)) != null) {
            onPasswordError(error);
            return;
        }

        if (loginView != null) {
            loginView.showLoading();
        }
        if (userEngine != null) {
            userEngine.login(phone, password);
        }
    }


    @Override
    public void loginSuccess() {
        if (loginView != null) {
            loginView.hideLoading();
            loginView.showToast("登錄成功");
            loginView.loginSuccess();
        }
    }

    @Override
    public void registerSuccess() {
        if (loginView != null) {
            loginView.hideLoading();
            loginView.showToast("登錄成功");
            loginView.loginSuccess();
        }
    }

    public void loginFailed(String errorMsg) {
        if (loginView != null) {
            loginView.hideLoading();
            loginView.showToast(errorMsg);
        }
    }


    public void registerFailed(String errorMsg) {
        if (loginView != null) {
            loginView.hideLoading();
            loginView.showToast(errorMsg);
        }
    }

    @Override
    public void onSuccess(int id, Object data) {
        switch (id) {
            case UserEngine.ID_LOGIN:
                loginSuccess();
                break;

            case UserEngine.ID_REGISTER:
                registerSuccess();
                break;
        }
    }

    @Override
    public void onError(int id, int code, String msg) {
        switch (id) {
            case UserEngine.ID_LOGIN:
                loginFailed(msg);
                break;

            case UserEngine.ID_REGISTER:
                registerFailed(msg);
                break;
        }
    }


    /**
     * 驗證碼校驗成功
     */
    @Override
    public void verifySmsCodeSuccess(Object data) {
        if (loginView != null) {
            loginView.hideLoading();
            loginView.verifySmsCodeSuccess(data);
        }
    }

    /**
     * 獲取驗證碼成功
     */
    @Override
    public void sendSmsCodeSuccess(Object data) {
        if (loginView != null) {
            loginView.hideLoading();
            loginView.showToast("驗證碼發送成功,注意查收");
            loginView.sendSmsCodeSuccess(data);
        }
    }

    /**
     * 返回支持發送驗證碼的國家列表
     */
    @Override
    public void getSupportCountrySuccess(Object data) {
        if (loginView != null) {
            loginView.hideLoading();
            loginView.getSupportCountrySuccess(data);
        }
    }

    public void smsFailed(int event, Object data) {
        if (loginView != null) {
            loginView.hideLoading();
            String error = ((Throwable) data).getMessage();
            try {
                SmsError smsError = new Gson().fromJson(error, SmsError.class);
                error = smsError.getDetail();
            } catch (Exception e) {
                e.printStackTrace();
            }
            loginView.showToast(error);
        }
    }


    @Override
    public void smsSuccess(int event, Object data) {
        //回調完成
        if (event == SMSSDK.EVENT_SUBMIT_VERIFICATION_CODE) {
            verifySmsCodeSuccess(data);
        } else if (event == SMSSDK.EVENT_GET_VERIFICATION_CODE) {
            sendSmsCodeSuccess(data);
        } else if (event == SMSSDK.EVENT_GET_SUPPORTED_COUNTRIES) {
            getSupportCountrySuccess(data);
        }
    }

    @Override
    public void smsError(int event, Object data) {
        if (data != null && (data instanceof Throwable)) {
            smsFailed(event, data);
        }
    }


    @Override
    public void destroy() {
        loginView = null;
        if (smsEventHandler != null) {
            SMSSDK.unregisterEventHandler(smsEventHandler);
        }
    }

    private String checkCode(String code) {
        if (TextUtils.isEmpty(code)) {
            return ("請輸入驗證碼");
        }
        return null;
    }

    private String checkPhone(String phone) {
        if (TextUtils.isEmpty(phone)) {
            return ("請輸入手機號");
        }

        if (!StringUtils.isMobilePhone(phone.trim())) {
            return ("手機號格式有誤");
        }
        return null;
    }

    private String checkPassword(String password) {
        if (TextUtils.isEmpty(password.trim())) {
            return "密碼不能爲空";
        } else if (password.length() < 6) {
            return "密碼必須大於6位";
        }
        return null;
    }


    private static class SMSEventHandler extends EventHandler {
        SMSCallback callback;

        public SMSEventHandler(SMSCallback callback) {
            this.callback = callback;
        }

        @Override
        public void afterEvent(final int event, int result, final Object data) {
            if (result == SMSSDK.RESULT_COMPLETE) {
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        callback.smsSuccess(event, data);
                    }
                });
            } else {
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        callback.smsError(event, data);
                    }
                });
                ((Throwable) data).printStackTrace();
            }
        }
    }
}

上面的代碼有詳細的註釋,方法名定義的也比較形象,一看就知道幹什麼的。

如果明白了MVP架構的流程圖,上面的代碼雖然有點長,但是非常很好理解:

Activity 實現了ILoginView接口,所以Activity就是View 層;

Model層呢,在這裏就是請求網絡。

我們並沒有讓Activity和Model直接交互,而是通過Presenter層來作爲溝通橋樑的。

所以Activity裏組合了Presenter:

public class UserLoginActivity extends BaseActivity<UserLoginBinding> implements ILoginView
        , MyCountDownTimer.CountDownCallback {


    private static final int ACTION_TYPE_LOGIN = 1;
    private static final int ACTION_TYPE_REGISTER = 2;

    private ILoginPresenter loginPresenter; //Presenter

    //省略其他代碼...
在Presenter中組合了ILoginView

public class LoginPresenterImpl implements ILoginPresenter,
        BaseEngine.Callback, SMSCallback {

    private UserEngine userEngine;//相當於Model

    private ILoginView loginView;

    //省略其他代碼...

}

這樣Presenter和View之間包含了彼此,就可以彼此通信了。這樣就把Activity(View) 和 Model之間實現了低耦合。

使用MVP架構需要注意的地方

1)釋放資源(在用戶離開界面的時候,記得釋放Presenter的資源)

@Override
public void destroy() {

    loginView = null;

    userEngine.destroy();

    if (smsEventHandler != null) {
        SMSSDK.unregisterEventHandler(smsEventHandler);
    }
}

2)方法命名/有些方法應該放在IView接口裏還是接口的實現者Activity裏的問題

我也看了其他一些MVP的例子,很多也是基於登錄的例子,比如在登錄成功後,跳到主界面,在IView接口定了 goMain()或goMainActivity() 我覺得這樣是不妥的。

假設用戶登錄成功後的邏輯變了,不跳到主界面了而是其他的邏輯了,那在ILoginView頂層接口裏定義goMain()或goMainActivity()就不合適了,因爲用不到了,總不能在goMain方法裏不寫跳到主界面而是寫其他代碼的邏輯吧,這樣方法名和裏面的代碼就不匹配了。如果更改方法名,那所以實現該接口的類都需要改方法名了,耦合又變大了。

我的建議是,既然是登錄成功的邏輯,那就在ILoginView接口裏定義loginSuccess()方法,不管以後登錄成功做什麼邏輯都可以在loginSuccess()裏實現。而不應該直接把業務邏輯和方法名綁定死了,並且還把綁定死的方法名放在ILoginView頂層接口裏,因爲業務邏輯是很容易發生變化的。所以應該定義一個通用的方法名loginSuccess()。

有人說goMain()或goMainActivity(),你看多清晰,一看我就知道是幹什麼的。但是loginSuccess()不知道做什麼邏輯,還得詳細看loginSuccess()裏的代碼。 我覺得考慮的也有一定的道理,但是這不能作爲把業務邏輯和方法名綁定死了放到頂層接口裏的理由。
如果你出於這種考慮也很好解決的,直接在實現ILoginView的Activity裏定義 goMain() 或 goMainActivity() 方法,然後在loginSuccess()裏調用goMain()或goMainActivity()。

3)有些代碼可以放在Activity又可以放到Presenter,該怎麼抉擇?

我們在使用驗證碼註冊的時候,當用戶點擊獲取驗證碼的時候,開發者可能就下意識的就在在Activity中調用SMSSDK的API來獲取驗證碼,然後在Activity中處理獲取成功和失敗的邏輯。

乍一看功能實現沒問題,我們可以在Activity中調用SMSSDK的API來獲取驗證碼,也可以在Presenter裏調用SMSSDK的API來獲取驗證碼。在使用MVP架構的時候我們時常遇到這樣的抉擇,在哪裏寫都可以實現功能,我們該怎麼抉擇呢?

其實這個時候SMSSDK(只不過SMSSDK是第三方提供的API而已)就是充當着Model的角色,因爲SMSSDK就是去請求網絡,然後發送驗證碼的,如果我們直接在Activity(View)中調用了,那麼Model和View又耦合了,所以應該放在Presenter中來做。假設有一天發送驗證碼的SDK我們替換成了其他第三方的SDK了,在View層我們不需要修改一行代碼,只需要修改Presenter就可以了。

通過上面一個登錄的例子,有些讀者看到接口中這麼多方法,瞬間感覺這個MVP不僅沒有幫我們解決問題,反而是問題的製造者。不是這樣的!假設我們使用熟悉的MVC開發的時候,我們可能是這樣的實現的:

//由於篇幅的原因,省略了大量的代碼,保留主要邏輯代碼
public class UserLoginActivity extends BaseActivity<UserLoginBinding> {

    private UserEngine userEngine;

    //請求登錄接口
    private void requestLogin() {
        showLoadingDialog();
        userEngine.login(xxx);
    }

    //請求註冊接口
    private void requestRegister() {
        showLoadingDialog();
        userEngine.register(xxx);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_user_login:
                requestLogin();
                break;
            case R.id.tv_register:
                requestRegister();
                break;
        }
    }

    @Override
    public void onSuccess(int id, Object content) {
        super.onSuccess(id, content);
        switch (id) {
            case userEngine.ID_LOGIN:
                //處理登錄成功後的邏輯
                break;
            case userEngine.ID_REGISTER:
                //處理註冊成功後的邏輯
                break;
        }
    }

    @Override
    public void onError(int id, String msg) {
        super.onError(id, msg);
    }

}

上面的代碼直接Activity中操作Model,耦合變得比較大了。而且隨着業務的增多Activity的代碼會越來越臃腫。

其實MVP也就是在上面的代碼之上做出一些改良而已,上面我們在View中直接操作Model,現在呢?MVP只不過是View和Model不直接通信了,通過Presenter來通信,Activity(View)操作Presenter,Presenter來操作Model,這樣View和Model實現了間接的通信了。

聰明的讀者可能又有問題了,分層(View、Presenter)可以啊,但是也不至於每層都設計一個接口吧?那麼以接口的形式給我們帶來什麼好處了呢?

1)代碼更加清晰

這樣在一定程度上避免了開發者在一個方法裏有太多的代碼,因MVP強制要求把各個業務都封裝在方法裏然後調用。

2)做好業務的頂層設計

在開發的時候,當產品原型出來通過了評審後,因爲業務都出來了嘛,開發這塊就可以把View、Presenter接口的各個函數名全部設計好,做好頂層設計,這也倒逼開發者對這個需求這塊一定要有着全面的理解。如果對業務不瞭解是無法設計出接口的。到時候往方法裏填代碼就可以了。

3)解耦

我想大家應該注意到了,在View一層我們使用Presenter的時候都是通過IPresenter接口來定義的,而不是該接口的實現着。這樣做方便後面替換,這也是面向接口編程的好處。到時候業務邏輯變了,我們只要重新實現IPresenter接口,然後在View中替換下IPresenter的實現者即可,而View層不需要修改代碼。
MVP模式實現文章詳情

如果對上面的註冊的例子理解了,實現這個功能就非常簡單了。由於篇幅的原因,具體的代碼細節就不帖出來了。把代碼的接口設計出來。

該界面主要有加載文章、評論列表、評論、分享等功能.

public interface IArticleDetailView extends BaseView{
    //加載文章失敗
    void onLoadArticleFailed();
    //加載文章成功
    void onLoadArticleSuccess(Article article);
    //加載評論列表
    void onLoadCommentSuccess(Object data);
    //加載評論列表失敗
    void onLoadCommentFailed(String msg);
    //添加評論成功
    void addCommentSuccess(Comment comment);
    //添加評論失敗
    void addCommentFailed(String errorMsg);
    //分享
    void shareArticle();
}

//在Activity中實現IArticleDetailView接口細節我不貼出來了,根據要求實現即可。


//下面是Presenter接口

public interface IArticleDetailPresenter extends BasePresenter{
    //加載文章
    void loadArticle(Long id);
    //文章加載成功
    void loadArticleSuccess(Article article);
    //文章加載失敗
    void loadArticleFailed(String error);
    //加載文章評論列表
    void loadComments(int articleId);
    //加載文章評論列表成功
    void loadCommentSuccess(Object data);
    //加載文章評論列表失敗
    void loadCommentFailed(String msg);
    //添加評論
    void addComment(Comment comment);
    //添加評論成功
    void addCommentSuccess(Comment comment);
    //添加評論失敗
    void addCommentFailed(String errorMsg);
}

//至於Presenter調用Model的細節也不貼出來了。Model寫好了直接調用調用就可以了。

是不是很簡單,只要把頂層的接口設計出來,接下來就是在方法裏填代碼就可以了。
from:http://blog.csdn.net/johnny901114/article/details/54783106

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