Роберт Мартин, известный как «Дядя Боб», сформулировал «Принципы SOLID» — рекомендации, которым следует следовать при создании программного обеспечения, чтобы его было легче масштабировать и поддерживать. SOLID — это аббревиатура, и каждая буква обозначает принцип объектно-ориентированного проектирования. В этой статье мы сосредоточимся на букве «I» от SOLID, которая представляет идею разделения интерфейсов (interface segregation principle, ISP).
Так что же говорит принцип разделения интерфейсов:
Программные сущности не должны зависеть от методов, которые они не используют.
Иными словами, принцип разделения интерфейсов говорит о том, что слишком «толстые» интерфейсы необходимо разделять на более маленькие и специфические, чтобы программные сущности маленьких интерфейсов знали только о методах, которые необходимы им в работе.
Давайте возьмем пример интерфейса TextWatcher в Android. Этот интерфейс широко используется в разработке, когда мы хотим получать колбеки, если пользователь изменяет текст в любом View, наследнике TextView (например, в EditText). Мы можем добавить интерфейс TextWatcher в EditText, используя функцию addTextChangedListener.
Пример: необходимо отображать всплывающее сообщение с текущим текстом всякий раз, когда пользователь вводит символ в EditText.
Для этого нам нужно прикрепить интерфейс TextWatcher к EditText с помощью функции addTextChangedListener. Здесь мы вынуждены реализовать все 3 функции интерфейса TextWatcher.
Код будет выглядеть примерно так:
binding.editText.addTextChangedListener(object: TextWatcher { override fun beforeTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) { //NO OP } override fun onTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) { showToast(charSequence.toString()) } override fun afterTextChanged(charSequence: Editable?) { //NO OP } })
Но здесь нам нужна только функция onTextChanged для отображения нужного текста. Нам не нужны функции beforeTextChanged и afterTextChanged, и их реализация является нарушением принципа разделения интерфейсов.
Теперь возникает вопрос — как мы можем разделить функции в интерфейсе TextWatcher и использовать только функцию onTextChanged?
С помощью мощной функции расширения в Kotlin и использования inline функции, которая повышает эффективность кода Kotlin при использовании функций высшего порядка, давайте оптимизируем код, чтобы удовлетворить принцип разделения интерфейсов.
Начнем с создания новой Extension функции onTextChanged в классе EditText. Это добавит функцию в класс EditText, и мы сможем использовать ее везде, где нам нужна функциональность.
Посмотрите приведенный ниже код:
inline fun EditText.onTextChange(crossinline listener: (String) -> Unit) { this.addTextChangedListener(object: TextWatcher { override fun beforeTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) { //NO OP } override fun onTextChanged(charSequence: CharSequence?, p1: Int, p2: Int, p3: Int) { listener(charSequence.toString()) } override fun afterTextChanged(p0: Editable?) { //NO OP } }) }
Здесь мы добавляем лямбда-функцию listener в качестве параметра нашей функции расширения onTextChange. Слушатель принимает String в качестве аргумента. Всякий раз, когда вызывается колбек onTextChanged в TextWatcher, мы будем вызывать функцию listener и передавать ей строковый аргумент.
Эту “расширенную” функцию можно использовать с EditText, например:
binding.editText.onTextChange { showToast(it) }
Внутри onTextChange вы получите строку, которая передается из функции onTextChanged интерфейса TextWatcher, и мы можем добавить нашу функциональность для отображения тоста.
inline : Использование inline функции повышает производительность функции более высокого порядка. Inline функция указывает компилятору копировать параметры и функции в место вызова.
crossinline : Ключевое слово crossinline добавлено, чтобы избежать нелокальных возвратов. Без него нелокальное управление потоком выполнения в лямбдах запрещено. Чтобы избежать этого, мы используем ключевое слово crossinline перед лямбда-функцией listener.
Мы можем использовать тот же подход, когда пытаемся реализовать интерфейсы, которые содержат много функций, и нам нужно использовать только одну или две одновременно. Использование такого подхода сделает код более лаконичным и эффективным.
Спасибо за чтение!!! Удачного кодирования!!!