Сажаем контроллеры на диету. Android

от автора

Паттерн 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *