Недавно я захотел отрефакторить код, потому что проект должен использоваться несколькими школами, и UI каждой школы отличается, но функциональность почти одинаковая. Хотя использование веток Gradle может справиться с различиями в коде, кодовая логика в общих частях постоянно растёт, поэтому я захотел провести рефакторинг. Я взглянул на недавно популярный шаблон MVP и нашёл его очень подходящим. Однако многие материалы в интернете просто скопированы, а некоторые даже содержат ошибки, поэтому я захотел описать шаблон MVP на основе собственного понимания, чтобы другим было легче его усвоить. Сначала поговорим о шаблоне проектирования MVC, который всем наиболее знаком.

Шаблон MVC разделяет M-слой и V-слой; M-слой и V-слой взаимодействуют через C-слой.

Теперь давайте рассмотрим MVP. В моём тексте ниже имена, начинающиеся с I, означают интерфейсы (рисовать диаграмму вручную утомительно...)

Самое большое отличие MVP от MVC в том, что P-слой заменяет прежний C-слой и больше не управляет конкретными реализациями, а работает с интерфейсами. Таким образом, ни разработка несколькими разработчиками, ни частые изменения UI не повлияют на P-слой. Пока интерфейсы C- и V-слоёв остаются неизменными, при изменениях UI нужно только изменить реализацию V-слоя; реализацию C-слоя даже не нужно менять. Это делает код очень понятным и упрощает тестирование, потому что логический слой и слой представления полностью разделены. Позвольте объяснить кодом — начиная с шаблона MVC, на примере входа в систему. В Android C-слой и V-слой уже хорошо разделены: XML, который мы пишем, эквивалентен V-слою, а Activity — C-слою.

public interface ICallback{
  void receive(boolean success);
}
public class LoginModel{
  public void login(String name,String password,ICallback callback){
    WebApi.login(name,password,callback);
  }
}
public class LoginActivity extends Activity{
  private LoginModel mLoginModel;
  private EditText mUserNameEt;
  private EditText mPasswordEt;
  private Button mSubmitBtn;
  public void onCreate(......){
    mLoginModel = new LoginModel(...);
    mSubmitBtn.setOnClickListener(new OnClickListener(View view){
        mLoginModel.login(mUserNameEt.get...,mPasswordEt.get...,new ICallback(){
          public void receive(boolean success){
            if(success){
              startActivity(new Intent(this,MainActivity.class));
              finish();
            } else {
              Toast.makeText(this,"登录失败",Toast.LENGTH_SHORT).show();
            }
          }
        });
    });
  }
}

Давайте посмотрим, как написать это с помощью шаблона MVP. В комментарии под статьёй упоминалось написание класса контракта. Я разобрался с этим, и писать класс контракта действительно удобнее, поэтому приведённый ниже код — это то, что я переработал.

public interface ILoginContract {

  public interface ILoginModel{
    public void login(String name,String password,ICallback callback);
  }

  public interface ILoginPresenter{
    public void login(String name,String password);
  }

  public interface ILoginView{
    public void showDialog();
    public void dismissDialog();
    public void showToast(String message);
    public void navigateToMain();
  }
}
public class PresenterImpl implements ILoginPresenter,ICallback{
  private ILoginView mLoginView;
  private ILoginModel mLoginModel;

  public PresenterImpl(ILoginView loginView){
    this.mLoginView = loginView;
    this.mLoginModel = new LoginModelImpl();
  }

  public void login(String name,String password){
    if(isEmpty(name)||isEmpty(password)){
        this.mLoginView.showToast("用户名或密码不能为空");
        return;
    }
    this.mLoginModel.login(name,password,this);
  }

  public void receive(boolean success){
    if(success){
      this.mLoginView.navigateToMain();
    }else{
      this.mLoginView.showToast("登录失败");
    }
  }

  private boolean isEmpty(String text){
    return text==null||"".equals(text)?true:false;
  }
}
public class LoginActivity extends Activity implements ILoginView{
  private IPresenter mPresenter;
  private EditText mUserNameEt;
  private EditText mPasswordEt;
  private Button mSubmitBtn;
  public void onCreate(......){
    mPresenter = new PresenterImpl(this);
    mSubmitBtn.setOnClickListener(new OnClickListener(View view){
        mPresenter.login(mUserNameEt.getText().toString(),
                          mPasswordEt.getText().toString());
    });
  }

  public void showDialog(){
    //显示一个转圈的dialog;
  }

  public void dismissDialog(){
    //隐藏转圈的dialog;
  }

  public void showToast(String message){
    Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
  }

  public void navigateToMain(){
    startActivity(new Intent(this,MainActivity.class));
    finish();
  }
}

Шаблон MVP действительно требует больше классов и кода, но это определённо того стоит. Вы можете видеть, что логика кода пишется яснее, чем в шаблоне MVC, и тестирование кода также проще. Вы даже можете реализовать функции, когда у вас есть только функциональность и нет макетов UI, а изменения UI не повлияют на какой-либо бизнес-код.

Описанный выше шаблон проектирования MVP — это моё личное понимание. Если есть ошибки, пожалуйста, исправьте меня. Спасибо. Давайте обсуждать и улучшать вместе.

Отправил: oDynamicSupermano