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

时间:2019-05-06 11:47:58

标签: android design-patterns android-activity android-mvp

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

  1. 输入ID,然后发送至后端:firstCall(id)
    • 如果未找到ID,请请求正确/另一个ID
    • 如果ID正确,则继续执行步骤2。
  2. 输入密码并发送(哈希)到后端secondCall(password)
    • 如果密码错误,请再次请求(限制)
    • 如果密码正确,请继续3。
  3. 后端对此会话使用限时令牌进行响应(sessionToken)
    1. 使用此令牌做一件事

有1个wrongId()correctId()回调。 有用于2的wrongPassword()correctPassword()回调。

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

由于我在不同的活动(全部在MVP中)上使用了此登录过程,因此我有很多重复的代码。我将此登录信息提取到了一个separat类中,该类仍将在演示者中使用。但是,我有要求输入错误密码的问题吗?

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

1 个答案:

答案 0 :(得分:0)

您可以通过多种方法来执行此操作,但是实施会受到多种因素的影响。有关您的案例的更具体的示例,您需要提供有关系统及其要求的更多信息。另外一些示例代码也会有所帮助。

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

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

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窗口中输入密码)提供一些数据(例如,密码),因此您可以使用界面或回调从您的 登录过程发出请求em> ,以便某人向其提供所需的数据。

通过这种方式,您可以插入不同的视图和/或演示者以添加特定的逻辑(例如密码输入)并重复使用该过程。

这是一个例子:

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;
    }
}

这样,您可以插入不需要的每个视图/演示者。只需实现接口,该过程将在一个单独的对象中。如有必要,您可以创建一个实现此处接口的基本演示者。

最后一个具有 LoginProcess 类的解决方案具有最佳的Separation of Concerns并使用Single Responsibility Principle

问题在于您必须定义另一组接口和类。

第一个使用较少的代码,但仍然有效。您可以重用演示者,还可以创建一个实现 LoginView 界面的视图,该界面可以重用或用作基类。