如何避免 MVP 中的乒乓方法调用?
How to avoid ping pong method call in MVP?
在我的 Android 应用程序中,我有一个 MVP 模式的片段。假设我们有:
- CalculationFragment(视图)
- CalculationPresenter(演示者)
- 计算网络(模型)
我需要多步计算或 REST 调用:
- 在 CalculationFragment 的 onViewCreated() 中,我调用
val sessionId = presenter.firstCall() as String
来检索 sessionToken 以进行进一步处理。
- 然后演示者通过 REST 检索 sessionToken。到目前为止很酷。然后我需要链中的下一个调用,消耗检索到的 sessionToken 和
NfcAdapter.getDefaultAdapter(activity)
。
val jsonObject = secondCall(sessionId, nfcAdapter)
。
因为我是演示者,所以我这里既没有 activity 也没有 NfcAdapter(老实说我不想)。我这里有两个选择:
- Detour over view 在我的演示者中,我使用 sessionToken
view?.onFirstCallResult(sessionToken)
返回到我的片段,并立即从 CalculationFragment 的 onFirstCallResult()
调用 presenter.secondCall(sessionToken, NfcAdapter.getDefaultAdapter(activity))
。
- Short way, handled from pesenter 我已经在第一次调用中交出第二次调用的 activity/NfcAdapter 并将其存储在演示者中。我不需要经常在视图和演示者之间来回切换。此外,我可以在所有通话中都留在演示者中吗?
什么是优雅的解决方案/模式?
向 Presenter、Model 或 Command 添加更多逻辑,从而将其从视图。这通常是更好的方法。考虑 Single Responsibility principle 并从 View 中移动 application/domain 逻辑。
您可以通过以下几种方式执行此操作:
使用一个Command。 Presenter 将创建 并调用 它。当 Command 完成时,Presenter 将 return 结果发送到 View.
设计您的 Presenter 来完成您在第二张图中所做的工作。如果演示者很简单,那没关系。对于更复杂的场景,使用命令将执行逻辑的责任与何时应调用[=]的责任分开88=].
在您的情况下,您需要从 View 获取 NfcAdapter 到 Presenter和 Command(如果有的话)。
您可以通过以下几种方式完成此操作:
- 在 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
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 和 拦截器 。
在我的 Android 应用程序中,我有一个 MVP 模式的片段。假设我们有:
- CalculationFragment(视图)
- CalculationPresenter(演示者)
- 计算网络(模型)
我需要多步计算或 REST 调用:
- 在 CalculationFragment 的 onViewCreated() 中,我调用
val sessionId = presenter.firstCall() as String
来检索 sessionToken 以进行进一步处理。 - 然后演示者通过 REST 检索 sessionToken。到目前为止很酷。然后我需要链中的下一个调用,消耗检索到的 sessionToken 和
NfcAdapter.getDefaultAdapter(activity)
。
val jsonObject = secondCall(sessionId, nfcAdapter)
。
因为我是演示者,所以我这里既没有 activity 也没有 NfcAdapter(老实说我不想)。我这里有两个选择:
- Detour over view 在我的演示者中,我使用 sessionToken
view?.onFirstCallResult(sessionToken)
返回到我的片段,并立即从 CalculationFragment 的onFirstCallResult()
调用presenter.secondCall(sessionToken, NfcAdapter.getDefaultAdapter(activity))
。 - Short way, handled from pesenter 我已经在第一次调用中交出第二次调用的 activity/NfcAdapter 并将其存储在演示者中。我不需要经常在视图和演示者之间来回切换。此外,我可以在所有通话中都留在演示者中吗?
什么是优雅的解决方案/模式?
向 Presenter、Model 或 Command 添加更多逻辑,从而将其从视图。这通常是更好的方法。考虑 Single Responsibility principle 并从 View 中移动 application/domain 逻辑。
您可以通过以下几种方式执行此操作:
使用一个Command。 Presenter 将创建 并调用 它。当 Command 完成时,Presenter 将 return 结果发送到 View.
设计您的 Presenter 来完成您在第二张图中所做的工作。如果演示者很简单,那没关系。对于更复杂的场景,使用命令将执行逻辑的责任与何时应调用[=]的责任分开88=].
在您的情况下,您需要从 View 获取 NfcAdapter 到 Presenter和 Command(如果有的话)。
您可以通过以下几种方式完成此操作:
- 在 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
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 和 拦截器 。