Паттерн MVС появился достаточно давно и создавался с целью разделения бизнес-логики приложения от представления. Но далеко не все программисты реализуют его правильно, из-за чего возникают «Толстые тупые уродливые контроллеры» содержащие тонны кода. В этой статье пойдет речь о правильной реализации View классов, для того чтобы уменьшить количество кода в контроллерах и оставить место чистой бизнес-логике приложения.

Все, наверное, должны знать что MVC бывает двух типов — с активной моделью и пассивной, различие которых кроется в том, что пассивная модель служит простым источником данных (как, например, DAO для базы данных), а активная модель сама обновляет состояние своих подписчиков — View. Пассивная модель является более универсальной и простой, кроме того чаще всего используется в разработке, поэтому она будет использоваться для примера в этой статье. Давайте взглянем на её схему.

Пользователь взаимодействует с контроллером, контроллер запрашивает данные у модели и заполняет View, который отображается пользователю, всё просто.
Если с контроллером и моделью, вроде бы, всё понятно, то со View возникают некоторые трудности, главная их причина — View, как такого, нет, никто не задумывается о создании отдельных View классов с интерфейсом, через который контроллер мог бы передавать данные для отображения. Большинство просто создаёт xml разметку и заполняет её прямо в контроллере, из-за чего код, который по идее должен содержать бизнес-логику переполняется деталями отображения, такими как цвет текста, размер шрифта, установка текста в TextView, работа с ActionBar'ом, NavigatonDrawer'ом и прочими. В результате код Activity разрастается до 1000 строк и на первый взгляд содержит какой-то мусор.
Давайте взглянем на то, как делается типичное Android приложение без создания отдельных View классов, и на другое, в котором в полной мере используется View.
Наше приложение будет решать вполне распространенную задачу — загружать и отображать профайл пользователя. Начнем реализацию.
Для этого создадим модельный класс User, в котором будет храниться имя и фамилия пользователя.
И класс provider, который будет её «загружать». Этот класс создан для демонстрационных целей, в реальном проекте не следует использовать AsyncTask для загрузки данных и не стоит писать свой велосипед, который даже не учитывает жизненный цикл Activity и не обрабатывает ошибки, лучше использовать готовое решение, например, RoboSpice. Здесь этот класс нужен, по большей части, только для того, чтобы скрыть детали реализации загрузки данных в отдельном потоке.
Далее создается xml верстка, которую мы опустим и контроллер, который должен связать View и Model, и внести немного бизнес-логики в наше приложение. В виде контроллера выступает Activity, обычно он реализуется примерно так:
При открытии экрана начинается загрузка профайла, отображается progress bar, когда профайл будет загружен, progress bar скрывается и происходит наполнение экрана данными.
Как видно из этого кода — в нём перемешивается работа с представлением и бизнес-логика.
Если сейчас все выглядит не так плохо, то при развитии проекта такой код станет плохочитаемым и трудноподдерживаемым.
Давайте вспомним про ООП и добавим немного абстракции в наш код.
View берет на себя всю работу с представлением Activity. Для отображения профайла пользователя нужно просто воспользоваться методом showUser(User) и передать ему модельный объект. В реальном проекте для View желательно создать базовый класс, в который можно перенести вспомогательные методы, такие как showProgressBar(), hideProgressBar(), и другие. В результате вся логика работы с представлением вынесена из Activity в отдельную сущность, что в разы уменьшает объемы кода контроллера и создаёт прозрачную абстракцию работы с View.
Activity же теперь ничего не знает о TextView и других контролах. Все взаимодействие с представлением происходит с помощью класса UserView и его интерфейса.
Теперь контроллер оперирует всего двумя сущностями — UserView и UserProvider, в нём нет тонкостей реализации отображения данных. Код стал чище и понятней.
Сейчас класс UserView просто отображает данные, возможно вы захотите сделать сохранение состояния между поворотами экранов — этот вопрос можно легко решить создав метод, записывающий состояние View в Parcelable или Bundle. Также, скорей всего, понадобится возможность обработки нажатий, в этом случае сам OnClickListener лучше создать во View классе и в него передать Callback, который реализует ваш контроллер.
Вот, собственно, и все. Так решается проблема недооценённых View в Android. При использовании этого подхода количество кода в ваших контроллерах заметно уменьшится, уровень абстракций возрастет и доллар опять будет стоить 30 рублей.
Читайте также:
Стилизация iOS-приложений: как мы натягиваем шрифты, цвета и изображения
Архитектурный дизайн мобильных приложений
Архитектурный дизайн мобильных приложений: часть 2

Все, наверное, должны знать что MVC бывает двух типов — с активной моделью и пассивной, различие которых кроется в том, что пассивная модель служит простым источником данных (как, например, DAO для базы данных), а активная модель сама обновляет состояние своих подписчиков — View. Пассивная модель является более универсальной и простой, кроме того чаще всего используется в разработке, поэтому она будет использоваться для примера в этой статье. Давайте взглянем на её схему.

Пользователь взаимодействует с контроллером, контроллер запрашивает данные у модели и заполняет View, который отображается пользователю, всё просто.
- При использовании MVC в Android, Activity или Fragment является контроллером.
- Модель — набор классов, которые служат источником данных приложения.
- View — xml разметка и кастомные View компоненты, на подобие Button и т. д.
Если с контроллером и моделью, вроде бы, всё понятно, то со View возникают некоторые трудности, главная их причина — View, как такого, нет, никто не задумывается о создании отдельных View классов с интерфейсом, через который контроллер мог бы передавать данные для отображения. Большинство просто создаёт xml разметку и заполняет её прямо в контроллере, из-за чего код, который по идее должен содержать бизнес-логику переполняется деталями отображения, такими как цвет текста, размер шрифта, установка текста в TextView, работа с ActionBar'ом, NavigatonDrawer'ом и прочими. В результате код Activity разрастается до 1000 строк и на первый взгляд содержит какой-то мусор.
Давайте взглянем на то, как делается типичное Android приложение без создания отдельных View классов, и на другое, в котором в полной мере используется View.
Наше приложение будет решать вполне распространенную задачу — загружать и отображать профайл пользователя. Начнем реализацию.
Для этого создадим модельный класс User, в котором будет храниться имя и фамилия пользователя.
public class User {
private final String firstname;
private final String lastname;
public User(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
// getters
}
И класс provider, который будет её «загружать». Этот класс создан для демонстрационных целей, в реальном проекте не следует использовать AsyncTask для загрузки данных и не стоит писать свой велосипед, который даже не учитывает жизненный цикл Activity и не обрабатывает ошибки, лучше использовать готовое решение, например, RoboSpice. Здесь этот класс нужен, по большей части, только для того, чтобы скрыть детали реализации загрузки данных в отдельном потоке.
public class UserProvider {
// результат вернем в Callback
public void loadUser(Callback callback) {
new LoadUserTask(callback).execute();
}
public class LoadUserTask extends AsyncTask<Void, Void, User> {
private Callback callback;
public LoadUserTask(Callback callback) {
this.callback = callback;
}
@Override
protected User doInBackground(Void... params) {
User user = new User("firstname", "lastname");
return user;
}
@Override
protected void onPostExecute(User user) {
super.onPostExecute(user);
callback.onUserLoaded(user);
}
}
public interface Callback {
void onUserLoaded(User user);
}
}
Далее создается xml верстка, которую мы опустим и контроллер, который должен связать View и Model, и внести немного бизнес-логики в наше приложение. В виде контроллера выступает Activity, обычно он реализуется примерно так:
public class UserProfileActivity extends Activity implements Callback {
private TextView firstnameTxt, lastnameTxt;
private ProgressBar progressBar;
private UserProvider userProvider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_profile);
firstnameTxt = (TextView) findViewById(R.id.firstname);
lastnameTxt = (TextView) findViewById(R.id.lastname);
progressBar = (Progressbar) findViewById(R.id.progressBar);
userProvider = new UserProvider();
loadUser();
}
@Override
public void onUserLoaded(User user) {
hideProgressBar();
showUser(user);
}
private void loadUser() {
showProgressBar();
userProvider.loadUser(this);
}
public void showUser(User user) {
firstnameTxt.setText(user.getFirstname());
lastnameTxt.setText(user.getLastname());
}
public void showProgressBar() {
progressBar.setVisibility(View.VISIBLE);
}
public void hideProgressBar() {
progressBar.setVisibility(View.INVISIBLE);
}
}
При открытии экрана начинается загрузка профайла, отображается progress bar, когда профайл будет загружен, progress bar скрывается и происходит наполнение экрана данными.
Как видно из этого кода — в нём перемешивается работа с представлением и бизнес-логика.
Если сейчас все выглядит не так плохо, то при развитии проекта такой код станет плохочитаемым и трудноподдерживаемым.
Давайте вспомним про ООП и добавим немного абстракции в наш код.
public class UserView {
private final TextView firstnameTxt, lastnameTxt;
private final ProgressBar progressBar;
public UserView(View rootView) {
firstnameTxt = (TextView) rootView.findViewById(R.id.firstname);
lastnameTxt = (TextView) rootView.findViewById(R.id.lastname);
progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);
}
public void showUser(User user) {
firstnameTxt.setText(user.getFirstname());
lastnameTxt.setText(user.getLastname());
}
public void showProgressBar() {
progressBar.setVisibility(View.VISIBLE);
}
public void hideProgressBar() {
progressBar.setVisibility(View.INVISIBLE);
}
}
View берет на себя всю работу с представлением Activity. Для отображения профайла пользователя нужно просто воспользоваться методом showUser(User) и передать ему модельный объект. В реальном проекте для View желательно создать базовый класс, в который можно перенести вспомогательные методы, такие как showProgressBar(), hideProgressBar(), и другие. В результате вся логика работы с представлением вынесена из Activity в отдельную сущность, что в разы уменьшает объемы кода контроллера и создаёт прозрачную абстракцию работы с View.
Activity же теперь ничего не знает о TextView и других контролах. Все взаимодействие с представлением происходит с помощью класса UserView и его интерфейса.
public class UserProfileActivity extends Activity {
private UserView userView;
private UserProvider userProvider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user);
userView = new UserView(getWindow().getDecorView())
userProvider = new UserProvider();
loadUser();
}
@Override
public void onUserLoaded(User user) {
userView.hideProgressBar();
userView.showUser(user);
}
private void loadUser() {
userView.showProgressBar();
userProvider.loadUser(this);
}
}
Теперь контроллер оперирует всего двумя сущностями — UserView и UserProvider, в нём нет тонкостей реализации отображения данных. Код стал чище и понятней.
Сейчас класс UserView просто отображает данные, возможно вы захотите сделать сохранение состояния между поворотами экранов — этот вопрос можно легко решить создав метод, записывающий состояние View в Parcelable или Bundle. Также, скорей всего, понадобится возможность обработки нажатий, в этом случае сам OnClickListener лучше создать во View классе и в него передать Callback, который реализует ваш контроллер.
Вот, собственно, и все. Так решается проблема недооценённых View в Android. При использовании этого подхода количество кода в ваших контроллерах заметно уменьшится, уровень абстракций возрастет и доллар опять будет стоить 30 рублей.
Читайте также:
Стилизация iOS-приложений: как мы натягиваем шрифты, цвета и изображения
Архитектурный дизайн мобильных приложений
Архитектурный дизайн мобильных приложений: часть 2