Перед прочтением данной статьи настоятельно рекомендую ознакомиться с предыдущей статьей, в которой мы рассмотрели базовый шаблон проектирования MVC.
Итак, в предыдущей статье мы рассмотрели шаблон проектирования MVC модель, представление и контроллер. Этот шаблон не является универсальным, так как связи между слоями таковы — контроллер передает данные модели, модель обновляет данные и передает их представлению. В предыдущей статье мы отметили, что MVC не совсем подходит для разработки андроид приложений, так как в современных мобильных устройствах пользователь взаимодействует с экраном, на котором и видит данные, т.е. экран мобильного устройства это и контроллер и представление.
Именно поэтому был создан шаблон проектирования MVP. Прочитаем описание в википедии.
MVP — шаблон проектирования пользовательского интерфейса, который был разработан для облегчения автоматического модульного тестирования и улучшения разделения ответственности в презентационной логике (отделения логики от отображения):
- Модель (англ. Model) — хранит в себе всю бизнес-логику, при необходимости получает данные из хранилища.
- Вид (англ. View) — реализует отображение данных (из Модели), обращается к Presenter за обновлениями.
- Представитель (англ. Presenter) — реализует взаимодействие между моделью и представлением.
Теперь пользователь взаимодействует с видом (представлением), данные передаются презентеру, дальше они передаются в модель, в нем происходит обработка и возврат в презентер и из него уже передаются данные в представление.
Суть шаблона MVP именно в том, что представление и модель данных не имеют прямой связи и «ничего не знают друг о друге». А единым связующим звеном является презентер.
Давайте рассмотрим простейший код для разработки андроид.
1 2 3 4 5 6 7 8 |
public interface View { void showProgress(); void hideProgress(); void showData(CustomObject object); } |
Выделяем интерфейс, в котором будут методы отображения прогресса, скрытия и отображение данных (на вход будет принимать некий объект, содержащий необходимые для отображения данные).
Дальше необходим интерфейс для модели. В нашем случае это будет получение данных из сети.
1 2 3 4 5 6 7 8 9 10 |
public interface Model { void getData(String query); void setCallback(Callback callback); interface Callback { void returnData(CustomObject object); } } |
Реализация модели может выглядеть таким образом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class ModelImpl implements Model { private ModelCallback mCallback; @Override public void setCallback(Callback callback) { mCallback = callback; } @Override public void getData(String query) { CustomObject object; //get some data if (mCallback != null) mCallback.returnData(object); } |
Мы создаем некий интерфейс колбека, с помощью сеттера его инициализируем и когда наш метод получения данных заканчивает свою работу, то отдаем колбеку данные.
Теперь написать интефейс для презентера не составит труда, так как там должен быть всего 1 метод.
1 2 3 |
public interface Presenter { void getData(String query); } |
Теперь напишем реализацию презентера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class PresenterImpl implements Presenter, Model.Callback { private final View view; private final Model model; public PresenterImpl(View view) { this.view = view; this.model = new ModelImpl(); } @Override public void getData(String query) { this.view.showProgress(); this.model.setCallback(this); this.model.getData(query); } @Override public void returnData(CustomObject object) { this.view.hideProgress(); this.view.showData(object); } } |
Все, что делает презентер, так это получает данные из вью и передает их модели, поэтому никакой логики в реализации презентера нет и быть не может. Презентер лишь тонкая прослойка между вью и моделью.
Теперь напишем реализацию вью. В андроид это будет Активити.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
public class MainActivity extends Activity implements View { private ProgressBar progressBar; private EditText inputEditText; private TextView showingDataTextView; private Button getDataButton; private Presenter presenter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.presenter = new PresenterImpl(this); this.progressBar = findViewById(R.id.progress); this.inputEditText = findViewById(R.id.input_edit_text); this.showingDataTextView = findViewById(R.id.show_text_view); this.getDataButton = findViewById(R.id.get_data_button); this.getDataButton.setOnClickListener((View v) -> { this.presenter.getData(inputEditText.getText().toString()); }); } @Override public void showProgress() { this.progressBar.setVisibility(View.VISIBLE); } @Override public void hideProgress() { this.progressBar.setVisibility(View.GONE); } @Override public void showData(CustomObject object) { this.showingDataTextView.setText(object.getText()); } } |
Итак, на экране есть поле ввода, текст для отображения (может быть что угодно) и кнопка, при нажатии на которую идет поиск. В методе onCreate инициализируем презентер, элементы отображения и устанавливаем обработчик нажатия на кнопку, при котором лишь вызываем метод у презентера. Переопределяем методы представления в каждом из которых прописываем код для отображения.
Как видим в каждом классе количество кода минимально и предельно понятно для чтения. Большего всего кода будет лишь в классе Модели, так как там будет происходить запрос-ответ от сервера. Как видим, если нам необходимо будет также отобразить некую ошибку (например сервер недоступен), то для этого необходимо добавить метод в интерфейс вью, плюс добавить метод возврата ошибки из модели в колбек. Который в презентере будет обработан и отдан вью на отображение. А в активити это например будет всплывашка (тоаст).
Самое замечательное в шаблоне проектирования MVP то, что можно писать простые тесты для каждого слоя. Необходимо лишь заменить один слой на предоставление мокированных (фальшивых/предопределенных) данных и тестировать логику вызова методов другого слоя.