神刀安全网

Interfaces for presenters in MVP are a waste of time!

It’s been a long time since we started talking about MVP at Karumi. Today, the discussion is about whether creating an interface for the Presenter in MVP is really needed .

This is the Model View Presenter pattern’s schema:

Interfaces for presenters in MVP are a waste of time!

In this schema the Model box is related to all code needed to implement your business logic. The presenter is the class implementing the presentation logic and the view is an interface created to abstract the implementation view.

Why the view should be implemented with an interface in this pattern?Because we want to decouple the code from the implementation view. We want to abstract the framework used to write our presentation layer, regardless of any external dependency. We want to be able to easily change the implementation view if needed. We want to follow the dependency rule to improve unit testability. Please bear on mind that in order to follow the dependency rule, high level concepts -such as the presenter implementation, can’t depend on low level details like the implementation view.

Why the interface is needed to improve unit testability?Because to write a unit test, the entire code should be related to your domain as opposed to external systems such as a SDK or a framework.

Let’s illustrate this with a case scenario related to a login screen implemented for Android:

/** * Login use case. Given an email and password executes the login process. */ public class Login {    private LoginService loginService;    public Login(LoginService loginService) {     this.loginService = loginService;   }    public void performLogin(String email, String password, LoginCallback callback) {       boolean loginSuccess = loginService.performLogin(email, password);       if (loginSuccess) {         callback.onLoginSuccess();       } else {         callback.onLoginError();       }   } }  /** * LoginPresenter, where the presentation logic related to the login user interface is implemented. */ public class LoginPresenter {      private LoginView view;     private Login login;      public LoginPresenter(LoginView view, Login login) {         this.view = view;         this.login = login;     }      public void onLoginButtonPressed(String email, String password) {         if (!areUserCredentialsValid(email, password)) {             view.showInvalidCredentialsMessage();             return;         }          login.performLogin(email, password, new LoginCallback {             void onLoginSuccess() {                 view.showLoginSuccessMessage();             }              void onLoginError() {                 view.showNetworkErrorMessage();             }         });     }  }  /** * Declares what the presenter can do with the view without generating coupling to the view implementation details. */ public interface LoginView {    void showLoginSuccessMessage()   void showInvalidCredentialsMessage()   void showNetworkErrorMessage()  }  public class LoginActivity extends Activity implements LoginView {    .........  } 

Please don’t pay attention to the code syntax. I’ve written this from scratch and it’s almost pseudocode.

Why the interface view is needed here?It is needed to be able to write an unit test replacing the implementation view with a test double. Why is this needed in the unit test context? Because you don’t want to mock the Android SDK and use the LoginActivity inside your unit tests. Remember that any test where the Android SDK is part of the SUT is not a unit test.

Once this part of the implementation is clear, we need an interface not to depend on the implementation view.

Some developers have decided to also add an interface on top of the presenter. If we follow the previous example, the implementation could look like this:

public interface LoginPresenter {    void onLoginButtonPressed(String email, String password); }  public class LoginPresenterImpl implements LoginPresenter {     .... } 

or

public interface ILoginPresenter {    void onLoginButtonPressed(String email, String password); }  public class LoginPresenter implements ILoginPresenter {     .... } 

What’s the issue with this extra interface?IMHO this interface is not needed and it is just adding complexity and noise to the development. Why?

  • Look at the class name. When the interface is not needed, the names used become weird and do not add semantic value to the code.
  • That interface is the class we have to modify to add a new method when the presentation logic has a new path. Once done, we have to also update the implementation. Even when we use modern IDEs, this is a whole waste of time.
  • The navigation in the code could be difficult to follow. This is because whenever you are within the Activity (the implementation view) willing to navigate to the presenter, the file where you are directed next is the interface one but often, you want to reach is the implementation one instead.
  • The interface is not improving the project testability. The presenter class can be easily replaced with a test double using any mocking library or any manual test double. We don’t want to write a test using the activity as SUT and replacing the presenter with a test double.

So… What is the LoginPresenter interface adding here? Just noise :)

But… When should we use an interface?Interfaces should be used whenever we have more than one implementation (in this case the presenter implementation is just one). Also whenever we need to create a strong boundary between our code and a third party component like a framework or a SDK. Even without interfaces, we could use composition to generate abstraction. However, using an interface in Java is significantly easier. We recommend to add an interface if you have more than one implementation of the same thing or you want to generate a strong boundary. Otherwise, do not add any more code. The less code to maintain the better. Remember that the usage of interfaces is not the only way to decouple our code to generate abstraction

Wait… What if I want to decouple the implementation view from the presenter implementation?You simply don’t need to do that. The implementation view is a low level detail while the presenter implementation is a high level abstraction. Implementation details can be coupled to high level abstractions. You need to abstract your domain model from the framework where it’s executed, but you don’t want to abstract the other way around. Trying to reduce the coupling between the implementation view and the presenter one is just a waste of time.

I’ve created this gist to discuss about this topic. Please feel free to add any comment using code examples if needed :)

Extra ball: If you are thinking of different testing strategies for Android and the presentation layer, I wouldn’t use a unit test to test the presentation logic replacing the view with a test double. I’d use an approach like the one describedhere where the SUT is the whole presentation layer and not just the presenter one (the test doubles are used to replace the use case).

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Interfaces for presenters in MVP are a waste of time!

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址