Паттерн MVС появился достаточно давно и создавался с целью разделения бизнес-логики приложения от представления. Но далеко не все программисты реализуют его правильно, из-за чего возникают «Толстые тупые уродливые контроллеры» содержащие тонны кода. В этой статье пойдет речь о правильной реализации View классов, для того чтобы уменьшить количество кода в контроллерах и оставить место чистой бизнес-логике приложения.
Все, наверное, должны знать что 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
ссылка на оригинал статьи http://habrahabr.ru/post/257861/
Добавить комментарий