Сталкивался ли с Custom Delegate
Комментарии (3)
Ответ сгенерирован нейросетью и может содержать ошибки
Да, я сталкивался с Custom Delegate (Пользовательский делегат) при разработке на Android и активно использую этот паттерн. Это мощный и гибкий инструмент для декомпозиции логики, обеспечения чистой архитектуры и соблюдения принципа единой ответственности (Single Responsibility Principle, SRP).
Что такое Custom Delegate?
По своей сути, это реализация паттерна делегирования (Delegation Pattern). Вместо того чтобы класс сам выполнял все задачи, он передает (делегирует) часть своей ответственности другому объекту – делегату.
Зачем он нужен?
- Разделение ответственности: Класс фокусируется на своей основной роли (например, отображение данных в
RecyclerView.Adapter), а логика, связанная с данными (загрузка, трансформация, валидация), передается делегату. - Повторное использование: Один и тот же делегат с бизнес-логикой можно использовать в нескольких
Activity,Fragment,ViewModelили даже в разных модулях приложения. - Упрощение тестирования: Логику в делегате легко протестировать изолированно, без необходимости создавать тяжелые Android-компоненты.
- Улучшение читаемости: Код становится более структурированным и понятным.
Пример из практики: Adapter Delegates
Один из самых ярких примеров — библиотека AdapterDelegates (Hannes Dorfmann) или собственная реализация для RecyclerView.Adapter. Она идеально подходит для адаптеров с разными типами элементов (view types).
Представьте ленту, где есть посты с текстом, изображениями и видео. Вместо гигантского onCreateViewHolder и onBindViewHolder с кучей if/else, мы создаем делегат для каждого типа.
1. Базовый интерфейс делегата:
interface AdapterDelegate {
fun isForViewType(items: List<Any>, position: Int): Boolean
fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder
fun onBindViewHolder(holder: RecyclerView.ViewHolder, items: List<Any>, position: Int)
}
2. Делегат для текстового поста:
class TextPostDelegate(private val onItemClick: (Post) -> Unit) : AdapterDelegate {
override fun isForViewType(items: List<Any>, position: Int): Boolean {
return items[position] is TextPost
}
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_text_post, parent, false)
return TextPostViewHolder(view, onItemClick)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, items: List<Any>, position: Int) {
(holder as TextPostViewHolder).bind(items[position] as TextPost)
}
class TextPostViewHolder(
itemView: View,
private val onItemClick: (Post) -> Unit
) : RecyclerView.ViewHolder(itemView) {
fun bind(post: TextPost) {
itemView.titleTextView.text = post.title
itemView.contentTextView.text = post.content
itemView.setOnClickListener { onItemClick(post) }
}
}
}
3. Адаптер, использующий делегаты:
class FeedAdapter(private val delegates: List<AdapterDelegate>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var items = listOf<Any>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
delegates.forEachIndexed { index, delegate ->
if (delegate.isForViewType(items, position)) {
return index
}
}
throw IllegalArgumentException("No delegate found for position $position")
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return delegates[viewType].onCreateViewHolder(parent)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
delegates[getItemViewType(position)].onBindViewHolder(holder, items, position)
}
override fun getItemCount(): Int = items.size
}
Инициализация:
val adapter = FeedAdapter(
listOf(
TextPostDelegate { post -> /* Обработка клика */ },
ImagePostDelegate(),
VideoPostDelegate()
)
)
recyclerView.adapter = adapter
adapter.items = listOf(TextPost(...), ImagePost(...), VideoPost(...))
Другие сценарии использования
Делегаты для свойств (Property Delegation)
В Kotlin это встроенная концепция (by). Например, кастомный делегат для хранения SharedPreferences:
class PreferenceDelegate<T>(
private val preferences: SharedPreferences,
private val key: String,
private val defaultValue: T
) : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (defaultValue) {
is String -> preferences.getString(key, defaultValue) as T
is Int -> preferences.getInt(key, defaultValue) as T
is Boolean -> preferences.getBoolean(key, defaultValue) as T
else -> throw IllegalArgumentException("Type not supported")
}
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
with(preferences.edit()) {
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Boolean -> putBoolean(key, value)
else -> throw IllegalArgumentException("Type not supported")
}.apply()
}
}
}
// Использование
class SettingsManager(context: Context) {
private val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
var isDarkMode by PreferenceDelegate(prefs, "dark_mode", false)
var userName by PreferenceDelegate(prefs, "user_name", "")
}
Делегаты для навигации или обработки разрешений
Можно создать делегат PermissionDelegate, который инкапсулирует логику запроса разрешений в Activity/Fragment, освобождая их от этой рутины.
Вывод
Custom Delegate — это не какая-то одна конкретная библиотека, а архитектурный подход. Он позволяет создавать более модульные, тестируемые и поддерживаемые приложения. Его применение особенно оправдано в сложных адаптерах RecyclerView, для управления состоянием, работы с данными или инкапсуляции платформенных особенностей. Это признак зрелого подхода к разработке, когда код организуется вокруг ответственности, а не вокруг классов фреймворка.