如何将可重用的多步状态机实现到 MVP 中?

How to implement a reusable multistep state-machine into MVP?

在我的 Android 应用程序中,我有一个多步骤登录程序:

  1. 输入一个id,发送到后端:firstCall(id)
    • 如果没有找到id,请求correct/another id
    • 如果 id 正确,继续步骤 2。
  2. 输入密码并将(散列)发送到后端secondCall(password)
    • 如果密码错误,请重新请求(受限)
    • 如果密码正确,继续 3。
  3. 后端响应此会话的时间限制令牌 (sessionToken)
    1. 用这个令牌做一件事

1 有 wrongId()correctId() 个回调。 有 wrongPassword()correctPassword() 回调 2.

我已经通过使用 MVP 模式在我的 Android 应用程序中成功实现了这一点。

由于我在不同的活动(全部在 MVP 中)上使用了这个登录过程,所以我有很多重复的代码。我将此登录名提取到一个单独的 class,在演示者中仍会使用它。但是后来我遇到了请求错误密码的问题?

我是否需要在所有演示者中实施回调? 我如何通知当前使用的演示者密码错误等?

有多种方法可以做到这一点,但实施会受到多种因素的影响。对于您的案例的更具体示例,您需要提供有关系统及其要求的更多信息。一些示例代码也会有所帮助。

  • 如果您需要从多个地方登录,每个地方的视图是否不同?
  • 登录过程有什么不同,所有登录是相同的还是不同的?

实现此目的的一种方法是为所有支持登录的演示者使用基础 class。这是一个简化的示例(为简单起见,我将跳过对无效输入的检查和重试,您还必须提供更多详细信息和示例代码,以便给出适合您情况的更详细示例 ):

public interface Api {
    bool isIdValid(UUID id);
    Token getToken(UUID id, string password);
} 

public interface LoginView {

    UUID askUserForId();
    string askUserForPassword();

    void notifyForInvalidId();
    void notifyForInvalidPassword();

    void showNoMorePasswordAttemptsAllowed();
}

public class LoginPresenterBase {

    private Api mApi;
    private LoginView mLoginView;

    public LoginPresenterBase(LoginView loginView, Api api, other stuff){
        mApi = api;
        mLoginView = loginView;
    }

    public Token doLogin() {

        UUID id = null;

        while(true) {
            id = mLoginView.askUserForId();
            // add end condition here so you don't cycle forever when the user clicks 
               a cancel button or whatever
            if(!mApi.isIdValid(id)) {
                mLoginView.notifyForInvalidId();
            }
            else {
                break;
            }
        }

        for(int i = 0; i < retriesCount; ++i) {

            stirng password = mLoginView.askUserForPassword();   

            // exit if the user clicks a cancel button or closes the dialog.

            Token token = null;

            try {
                token = mApi.getToken(id, password);
            }
            catch(InvalidPasswordException) {

                if(i == retriesCount - 1){
                    // if last retry
                    mLoginView.showNoMorePasswordAttemptsAllowed();
                }
                else {
                    mLoginView.notifyForInvalidPassword();
                }
            }

            return token;
        }
    }
}

另一种方法是创建一个登录进程来表示步骤顺序。由于此过程需要从外部提供一些数据(例如密码)(例如用户在 UI window 中输入),您可以使用接口或回调从您的 登录过程 供某人提供所需数据。

通过这种方式,您可以 plug-in 不同的视图 and/or 演示者添加特定逻辑(例如密码输入)并重用流程。

这是一个例子:

public interace IdProvider {
    public UUID getId();
    public void invalidIdProvided(UUID);
}

public interface PasswordProvider {
    public string getPassword();
    public void invalidPasswordProvided(string password);
    public void maxPasswordAttemptsReached();
}

public class LoginProcess {

    private Api mApi;
    private IdProvider mIdProvider;
    private PasswordProvider mPasswordProvider;

    public LoginProcess(
        IdProvider idProvider, PasswordProvider passwordProvider, Api api) {

        mApi = api;
        mIdProvider = idProvider;
        mPasswordProvider = passwordProvider;
    }

    public Token execute() {

        UUID id = null;

        while(true) {
            id = mIdProvider.getId();

            if(!mApi.isIdValid(id)) {
                mIdProvider.invalidIdProvided(id);
              // add end condition here so you don't cycle forever when the 
              //  user clicks a cancel button or whatever
            }
            else {
               break;
            }
        }

        for(int i = 0; i < retriesCount; ++i) {

            string password = mPasswordProvider.getPassword();

            Token token = null;

            try {
                token = mApi.getToken(id, password);
            }
            catch(InvalidPasswordException) {

                if(i == retriesCount - 1){
                    // if last retry
                    mPasswordProvider.maxPasswordAttemptsReached();
                }
                else {
                    mPasswordProvider.invalidPasswordProvided();
                }
            }

            return token;
    }
}

这样你就可以 plug-in 每 view/presenter 你不想。只需实现接口,流程就会在一个单独的对象中。如有必要,您可以创建一个实现接口的基本演示器。

带有 LoginProcess class 的最后一个解决方案具有最好的 Separation of Concerns and uses the Single Responsibility Principle.

它的问题是您必须定义另一组接口和 classes.

第一个使用较少的代码并且仍然有效。您可以重用演示者,您可以创建一个实现 LoginView 接口的视图,您可以重用或用作基础 class .