Главная » Разработка для Android » Расширение классов Android

0

 

В то время как обратные вызовы обеспечивают ясные, хорошо продуманные средства для расширения поведений классов, есть и такие ситуации, в которых обратные вызовы не дают нужной степени гибкости. Очевидная проблема, возникающая с паттерном обратных вызовов, заключается в том, что иногда коду приходится взять на себя управление в той или иной ситуации, не предусмотренной разработчиками библиотек. Если служба не определяет обратного вызова, вам придется искать другой способ введения кода в поток управления. Одно из возможных решений – создание подкласса.

Некоторые классы в библиотеках Android разрабатывались специально для создания дочерних классов (например, класс BaseAdapter из android.widgets и AsyncTask, который мы опишем чуть ниже). Но вообще создание подклассов – не та задача, к которой можно относиться легкомысленно.

Подкласс может полностью заменить поведение любого нефинального метода в своем суперклассе и тем самым совершенно нарушить архитектурный контракт класса. В системе типов Java ничто не мешает, например, подклассу TextBox переопределить метод addTextChangedListener так, что он будет игнорировать собственный аргумент и не будет уведомлять клиентов обратного вызова об изменениях, происходящих в содержимом текстового поля. (В данном случае можно представить себе, например, реализацию «безопасного» текстового поля, содержимое которого скрыто.)

Такое нарушение контракта – а распознать детали контракта бывает непросто – может порождать ошибки двух разновидностей, и обе эти разновидности довольно нелегко находить. Первая и наиболее очевидная проблема возникает, когда разработчик использует нестандартный подкласс, создающий, например, описанное выше поле для безопасного ввода текста.

Предположим, разработчик создает вид, в котором содержится несколько виджетов, и применяет его метод addTextChangedListener к каждому виджету, регистрируя их на получение обратных вызовов. Но в ходе тестирования обнаруживается, что некоторые виджеты не работают так, как следует. Программист тратит несколько часов на изучение кода, пока наконец не обнаруживает, что метод просто ничего не делаеті И вдруг программиста осеняет, он заглядывает в исходный код виджета, чтобы убедиться, что в этом виджете на самом деле нарушен семантический контракт класса. Брр!

Но еще коварнее оказывается то обстоятельство, что и сам фреймворк Android может изменяться между следующими друг за другом версиями SDK. Может измениться реализация метода addTextChangedListener. Возможно, код в другой части фреймворка Android попробует вызвать addTextChangedListener, рассчитывая на его нормальное поведение. И вдруг, поскольку подкласс переопределяет метод, мы наблюдаем блистательный крах всего приложения!

Можно минимизировать вероятность возникновения подобной проблемы, вызывая сверхреализацию (superimplementation) для переопределенного метода таким образом:

Таким образом, вы гарантируете, что ваша реализация дополняет, но не заменяет собой существующее поведение, даже если со временем реализация суперкласса изменится. Существует правило написания кода, продвигаемое в некоторых сообществах разработчиков, которое формулируется как: «Разрабатывай в расчете на расширение». Правило постулирует, что все методы должны быть либо абстрактными, либо финальными. Это правило может показаться драконовским, но подумайте сами: ведь переопределяющий метод обязательно нарушит семантический контракт объекта и, чтобы не допустить этого, нужно как минимум вызвать сверхреализацию.

Источник: Android. Программирование на Java для нового поколения мобильных устройств

По теме:

  • Комментарии