主持人是否了解MVP模式中的活动/背景是一个坏主意?

我已经玩了几个星期的MVP模式,我已经到了需要上下文来启动service和访问Shared Preferences的地步。

我已经读过,MVP的目的是从逻辑上分离视图,并且在Presenter拥有context可能会打败这个目的(纠正我,如果我错了的话)。

目前,我有一个看起来像这样的LoginActivity:

LoginActivity.java

 public class LoginActivity extends Activity implements ILoginView { private final String LOG_TAG = "LOGIN_ACTIVITY"; @Inject ILoginPresenter mPresenter; @Bind(R.id.edit_login_password) EditText editLoginPassword; @Bind(R.id.edit_login_username) EditText editLoginUsername; @Bind(R.id.progress) ProgressBar mProgressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); MyApplication.getObjectGraphPresenters().inject(this); mPresenter.setLoginView(this, getApplicationContext()); } @Override public void onStart() { mPresenter.onStart(); ButterKnife.bind(this); super.onStart(); } @Override public void onResume() { mPresenter.onResume(); super.onResume(); } @Override public void onPause() { mPresenter.onPause(); super.onPause(); } @Override public void onStop() { mPresenter.onStop(); super.onStop(); } @Override public void onDestroy() { ButterKnife.unbind(this); super.onDestroy(); } @OnClick(R.id.button_login) public void onClickLogin(View view) { mPresenter.validateCredentials(editLoginUsername.getText().toString(), editLoginPassword.getText().toString()); } @Override public void showProgress() { mProgressBar.setVisibility(View.VISIBLE); } @Override public void hideProgress() { mProgressBar.setVisibility(View.GONE); } @Override public void setUsernameError() { editLoginUsername.setError("Username Error"); } @Override public void setPasswordError() { editLoginPassword.setError("Password Error"); } @Override public void navigateToHome() { startActivity(new Intent(this, HomeActivity.class)); finish(); } } 

演示者界面ILoginPresenter.java

 public interface ILoginPresenter { public void validateCredentials(String username, String password); public void onUsernameError(); public void onPasswordError(); public void onSuccess(LoginEvent event); public void setLoginView(ILoginView loginView, Context context); public void onResume(); public void onPause(); public void onStart(); public void onStop(); } 

最后,我的主持人:

LoginPresenterImpl.java

 public class LoginPresenterImpl implements ILoginPresenter { @Inject Bus bus; private final String LOG_TAG = "LOGIN_PRESENTER"; private ILoginView loginView; private Context context; private LoginInteractorImpl loginInteractor; public LoginPresenterImpl() { MyApplication.getObjectGraph().inject(this); this.loginInteractor = new LoginInteractorImpl(); } /** * This method is set by the activity so that way we have context of the interface * for the activity while being able to inject this presenter into the activity. * * @param loginView */ @Override public void setLoginView(ILoginView loginView, Context context) { this.loginView = loginView; this.context = context; if(SessionUtil.isLoggedIn(this.context)) { Log.i(LOG_TAG, "User logged in already"); this.loginView.navigateToHome(); } } @Override public void validateCredentials(String username, String password) { loginView.showProgress(); loginInteractor.login(username, password, this); } @Override public void onUsernameError() { loginView.setUsernameError(); loginView.hideProgress(); } @Override public void onPasswordError() { loginView.setPasswordError(); loginView.hideProgress(); } @Subscribe @Override public void onSuccess(LoginEvent event) { if (event.getIsSuccess()) { SharedPreferences.Editor editor = context.getSharedPreferences(SharedPrefs.LOGIN_PREFERENCES .isLoggedIn, 0).edit(); editor.putString("logged_in", "true"); editor.commit(); loginView.navigateToHome(); loginView.hideProgress(); } } @Override public void onStart() { bus.register(this); } @Override public void onStop() { bus.unregister(this); } @Override public void onPause() { } @Override public void onResume() { } } 

正如你所看到的,我将Activity的上下文传递给Presenter ,这样我就可以访问Shared Preferences 。 我很担心把主题传递给我的主持人。 这是一个好的事情吗? 或者我应该以其他方式做这件事?

编辑实现了Jahnold的第三select

所以让我们忽略接口和实现,因为它几乎是整个事情。 所以现在我正在injecting Sharedpreference的接口注入我的演示者。 这是我的AppModule代码

AppModule.java

 @Module(library = true, injects = { LoginInteractorImpl.class, LoginPresenterImpl.class, HomeInteractorImpl.class, HomePresenterImpl.class, } ) public class AppModule { private MyApplication application; public AppModule(MyApplication application) { this.application = application; } @Provides @Singleton public RestClient getRestClient() { return new RestClient(); } @Provides @Singleton public Bus getBus() { return new Bus(ThreadEnforcer.ANY); } @Provides @Singleton public ISharedPreferencesRepository getSharedPreferenceRepository() { return new SharedPreferencesRepositoryImpl(application.getBaseContext()); } } } 

我从上面的MyApplication.java获取上下文

当应用程序开始时,我确保用这行代码创build这个对象图:

 objectGraph = ObjectGraph.create(new AppModule(this)); 

这个可以吗? 我的意思是我现在不必将活动的背景传递给主持人,但是我仍然有这个应用的背景。

自从你问这个问题已经有一段时间了,但是我认为无论如何提供一个答案都是有用的。 我强烈build议主持人应该没有Android上下文(或任何其他Android类)的概念。 通过将您的Presenter代码与Android系统代码完全分离,您可以在JVM上对其进行testing,而无需模拟系统组件。

为了实现这一点,我认为你有三个select。

从视图访问SharedPreferences

这是我最不喜欢的三个访问SharedPreferences 不是一个视图操作。 但是它确实将Activity中的Android系统代码保留在Presenter之外。 在你的视图界面中有一个方法:

 boolean isLoggedIn(); 

可以从演示者那里调用。

使用Dagger注入SharedPreferences

由于您已经使用Dagger来注入事件总线,所以您可以将SharedPreferences添加到ObjectGraph中,并因此获得使用ApplicationContext构造的SharedPreferences实例。 这是你得到他们,而不必传递一个上下文到你的主持人。

这种方法的缺点是你仍然在传递一个Android系统类(SharedPreferences),当你想testingPresenter的时候就不得不嘲笑它。

创build一个SharePreferencesRepository接口

这是从Presenter中访问SharedPreferences数据的首选方法。 基本上你把SharedPreferences当成一个模型,并有一个存储库接口。

您的界面将类似于:

 public interface SharedPreferencesRepository { boolean isLoggedIn(); } 

然后你可以具体实现这个:

 public class SharedPreferencesRepositoryImpl implements SharedPreferencesRepository { private SharedPreferences prefs; public SharedPreferencesRepositoryImpl(Context context) { prefs = PreferenceManager.getDefaultSharedPreferences(context); } @Override public boolean isLoggedIn() { return prefs.getBoolean(Constants.IS_LOGGED_IN, false); } } 

这是SharedPreferencesRepository接口,然后您将Dagger注入到Presenter中。 这样,testing期间可以在运行时提供一个非常简单的模拟。 在正常操作期间,提供具体实现。

这个问题已经有人回答了,而且假设MVP的定义是OP在代码中使用的,那么@Jahnold的回答是非常好的。

然而,应该指出的是,MVP是一个高层次的概念,可以有许多遵循MVP原则的实现 – 有多种方法来保护猫。

还有另外一个MVP实现 ,它基于Android中的Activities不是UI元素 ,它将ActivityFragment指定为MVP主持人。 在这个configuration中,MVP主持人可以直接访问Context

顺便说一下,即使在前面提到的MVP实现中,我也不会使用Context来访问演示者中的SharedPreferences – 我仍然会为SharedPreferences定义一个包装类,并将其注入演示者。

大部分的领域元素,如数据库或networking,都需要构build上下文。 Thay不能在View中创build,因为View不能具有关于Model的任何知识。 他们必须在Presenter中创build。 他们可以通过Dagger注入,但也是使用Context。 因此在Presenter xP中使用上下文

如果我们想避免Presenter中的Context,那么我们可以使构造函数从Context创build所有这些Model对象而不保存它。 但在我看来,这是愚蠢的。 Android中的新JUnit可以访问上下文。

另一个缺点是使上下文可以为空,并且在域对象中应该有机制来提供在上下文中为空的情况下的testing实例。 我也不喜欢这个黑客。