【问题标题】:How to implement a reusable multistep state-machine into MVP?如何在 MVP 中实现可重用的多步状态机?
【发布时间】:2019-05-06 11:47:57
【问题描述】:

在我的 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 中),我有很多重复的代码。我将此登录名提取到一个单独的类中,该类仍将在演示者中使用。但是我遇到了请求错误密码的问题?

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

【问题讨论】:

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


    【解决方案1】:

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

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

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

    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 接口的视图,您可以重用或用作基类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多