如何避免 MVP 中的乒乓方法调用?

How to avoid ping pong method call in MVP?

在我的 Android 应用程序中,我有一个 MVP 模式的片段。假设我们有:

我需要多步计算或 REST 调用:

  1. 在 CalculationFragment 的 onViewCreated() 中,我调用 val sessionId = presenter.firstCall() as String 来检索 sessionToken 以进行进一步处理。
  2. 然后演示者通过 REST 检索 sessionToken。到目前为止很酷。然后我需要链中的下一个调用,消耗检索到的 sessionToken 和 NfcAdapter.getDefaultAdapter(activity)

val jsonObject = secondCall(sessionId, nfcAdapter)

因为我是演示者,所以我这里既没有 activity 也没有 NfcAdapter(老实说我不想)。我这里有两个选择:

  1. Detour over view 在我的演示者中,我使用 sessionToken view?.onFirstCallResult(sessionToken) 返回到我的片段,并立即从 CalculationFragment 的 onFirstCallResult() 调用 presenter.secondCall(sessionToken, NfcAdapter.getDefaultAdapter(activity))
  2. Short way, handled from pesenter 我已经在第一次调用中交出第二次调用的 activity/NfcAdapter 并将其存储在演示者中。我不需要经常在视图和演示者之间来回切换。此外,我可以在所有通话中都留在演示者中吗?

什么是优雅的解决方案/模式?

PresenterModelCommand 添加更多逻辑,从而将其从视图。这通常是更好的方法。考虑 Single Responsibility principle 并从 View 中移动 application/domain 逻辑。

您可以通过以下几种方式执行此操作:

  • 使用一个CommandPresenter创建调用 它。当 Command 完成时,Presenter 将 return 结果发送到 View.

  • 设计您的 Presenter 来完成您在第二张图中所做的工作。如果演示者很简单,那没关系。对于更复杂的场景,使用命令执行逻辑的责任与何时应调用[=]的责任分开88=].

在您的情况下,您需要从 View 获取 NfcAdapterPresenterCommand(如果有的话)。

您可以通过以下几种方式完成此操作:

  • Presenter
  • 的构造函数中传递
  • Presenter中添加一个特殊的initialization方法,并将它需要的所有依赖传递给这个方法(public void initialize(NfcAdapter adapter, ...))
  • View 添加一个方法,Presenter 可以在需要时调用它来获取适配器 (NfcAdapter view.getAdapter() ).
  • 将其作为参数传递给方法调用(就像您所做的那样);

选择一种方法取决于几个因素,开发人员的品味是其中之一 我个人会选择方法 1 或 2。我喜欢初始化对象的依赖项(Presenter 在这种情况下)在它的生命周期开始时,如果这个对象将一直需要它们并且它们不会改变。如果每次调用此方法时它们都更改,则将它们传递到方法调用中。在这种情况下,我认为您不会更改 NfcAdapter

让我们设计一个命令。因为你有一个相当笼统的描述并且没有描述确切的顺序(first_call(),second_call() 太笼统了),我将设计一个简单的非特定系统来进行几次调用。我将使用伪代码。大多数事情都是非特定的,因为我不知道 return 类型和东西。

我们将此命令称为 CalculateCommand。此命令将使用 CalculationModel 进行计算。接下来让我们定义一个 TokenService,它将包含获取令牌(API 调用)的逻辑。

public class TokenService {
   public SessionToken getToken() { ... }
}

public class CalculationResult {
  // represent whatever the result is...
}

public class CalculateCommand {

    private NfcAdapter mNfcAdapter;
    private TokenService mTokenService;
    private CalculationModel mCalculationModel;

    private SessionToken mSessionToken;

    public CalculateCommand(
      NfcAdapter nfcAdapter, 
      TokenService tokenService, 
      CalculationModel calculationModel) {

      mAdapter = adapter;
      mTokenService = tokenService;
      mCalculationModel = calculatioModel;
   }

   public CalculationResult Execute() {

     startSession();

     // do more stuff if you need to 

     val result = calculate();

     return result;
  }

  private void startSession() {
     mSessionToken = mTokenService.getToken();
  }

  private Result calcualte() {
    //not sure what parameters it needs but pass them here
    return mCalculatioModel.calculate(params...);
  }
}

public class Presenter {

    private View mView;
    private NfdAdapter mAdapter;
    private CalculationModel mModel;
    private TokenService mTokenService;

    public Presenter(View view, NfdAdapter adapter) {

      mView = view;
      mNfcAdapter = adapter;
      mModel = new CalculationModel();

     // or get if from a Service Locator, DI whatever.. if you need to mock the 
     // TokenService for unittests

     mTokenService = new TokenService(); 
   }

  public void performCalculation() {

    val cmd = CalculationCommand(mAdapter, mTokenService, mModel);

    val result = cmd.execute();

    mView.setResult(result);    
 }

 public class View {

    private Presenter mPresenter;

    public View() {
       mPresenter = new Presenter(this, NfcAdapter.getDefault(activity);
    }

    public void onViewCreated() {
       mPresenter.performCalculation();
    }

    public void setResult(Result result) {
       // do something with the result
    }
}

查看这些资源以获取有关 MVP 及其风格的更多信息:

https://martinfowler.com/eaaDev/uiArchs.html

https://www.martinfowler.com/eaaDev/PassiveScreen.html

https://martinfowler.com/eaaDev/SupervisingPresenter.html

I need a multi-step calculation or REST call:

In CalculationFragment's onViewCreated() I call val sessionId = presenter.firstCall() as String to retrieve a sessionToken for further process. Then the presenter retrieves the sessionToken via REST. So far so cool. Then I need the next call in chain, what consumes the retrieved sessionToken and a NfcAdapter.getDefaultAdapter(activity).

如果我正确理解你的问题,

获取 API 令牌然后自动进行另一个 REST API 调用的常见且可能是最合适的方法是使用 okhttp Authenticator 拦截器