Adapter — что это, паттерн преобразования интерфейсов для мобильной разработки

Автор: IT Sectr Опубликовано: 2026-02-18 Время чтения: 10 мин

Adapter — это структурный паттерн проектирования, преобразующий интерфейс одного класса в другой интерфейс, ожидаемый клиентом. В мобильной разработке Adapter чаще всего используется в RecyclerView.Adapter для Android и UITableViewDataSource для iOS. По данным Google I/O (2024), RecyclerView используется в 95% Android-приложений для отображения списков, и каждый требует собственной реализации Adapter.

Главное

  • Adapter — структурный паттерн, позволяющий объектам с несовместимыми интерфейсами работать вместе
  • RecyclerView.Adapter — реализация паттерна в Android, преобразующая данные в ViewHolder для списка
  • UITableViewDataSource — реализация в iOS, объединяющая паттерны Adapter и DataSource
  • Object Adapter использует композицию: адаптер содержит ссылку на адаптируемый объект
  • Class Adapter использует множественное наследование — возможен только в C++, недоступен в Java/Swift/Kotlin

Что такое паттерн Adapter?

Adapter — структурный паттерн, решающий проблему несовместимости интерфейсов. Он оборачивает один объект (Adaptee) в класс (Adapter) с интерфейсом, ожидаемым клиентом (Target). Клиент работает с Target, не зная о существовании Adaptee. Это разновидность паттерна Wrapper.

Kotlin
// Существующий класс с несовместимым интерфейсом
class LegacyAuthApi {
    fun loginWithToken(token: String): Map {
        return mapOf("status" to "ok", "user_id" to 42)
    }
}

// Целевой интерфейс (что ожидает клиент)
interface AuthService {
    suspend fun login(credentials: Credentials): Result
}

// Adapter — преобразует LegacyAuthApi в AuthService
class LegacyAuthAdapter(
    private val legacyApi: LegacyAuthApi
) : AuthService {
    override suspend fun login(credentials: Credentials): Result {
        val token = "${credentials.login}:${credentials.password}"
            .encodeToByteArray().let { Base64.encodeToString(it) }
        val response = legacyApi.loginWithToken(token)
        return if (response["status"] == "ok") {
            Result.success(User(response["user_id"] as Int))
        } else {
            Result.failure(AuthException("Login failed"))
        }
    }
}

LegacyAuthAdapter преобразует вызов login(credentials) в loginWithToken(token) для старого API. Клиент (ViewModel) работает через интерфейс AuthService и не знает, что под ним — новейший Firebase Auth или legacy API десятилетней давности. Это позволяет заменять реализации без изменения кода клиента.

Adapter в Android: RecyclerView и ListAdapter

RecyclerView.Adapter — самая распространённая реализация паттерна Adapter в Android. Он преобразует данные (список объектов) в ViewHolder'ы, которые RecyclerView отображает на экране. С появлением ListAdapter (Android Architecture Components) паттерн получил встроенную поддержку diff-расчёта для анимации изменений.

Kotlin
class UserAdapter(
    private val onItemClick: (User) -> Unit
) : ListAdapter(DiffCallback()) {

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    override fun onBindViewHolder(
        holder: UserViewHolder,
        position: Int
    ) {
        holder.bind(getItem(position), onItemClick)
    }

    class UserViewHolder(itemView: View) :
        RecyclerView.ViewHolder(itemView) {
        
        private val tvName = itemView.findViewById(R.id.tvName)
        
        fun bind(user: User, click: (User) -> Unit) {
            tvName.text = user.name
            itemView.setOnClickListener { click(user) }
        }
    }

    private class DiffCallback : DiffUtil.ItemCallback() {
        override fun areItemsTheSame(old: User, new: User) = old.id == new.id
        override fun areContentsTheSame(old: User, new: User) = old == new
    }
}

UserAdapter использует ListAdapter с DiffUtil для эффективной перерисовки списка. Когда данные обновляются, DiffCallback вычисляет разницу между старым и новым списком, и RecyclerView анимирует только изменившиеся элементы — без перерисовки всего списка. Это ключевое преимущество перед старшим ListView, который требовал ручного notifyDataSetChanged().

Adapter в iOS: UITableView и DiffableDataSource

UITableViewDataSource — это одновременно и паттерн Adapter, и DataSource. Он преобразует данные модели в ячейки таблицы. С iOS 13 Apple представила DiffableDataSource — современную замену, которая, подобно ListAdapter в Android, автоматически вычисляет изменения и анимирует обновления.

Swift
class UserTableViewAdapter: NSObject, UITableViewDataSource {
    var items: [User] = []
    
    func tableView(_ tableView: UITableView,
                    numberOfRowsInSection section: Int) -> Int {
        items.count
    }
    
    func tableView(_ tableView: UITableView,
                    cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(
            withIdentifier: "UserCell", for: indexPath)
        let user = items[indexPath.row]
        cell.textLabel?.text = user.name
        return cell
    }
}

// Современная версия: DiffableDataSource
enum Section { case main }

class ModernUserAdapter {
    var dataSource: UITableViewDiffableDataSource<Section, User>!
    
    func configure(for tableView: UITableView) {
        dataSource = UITableViewDiffableDataSource(
            tableView: tableView
        ) { tableView, indexPath, user in
            let cell = tableView.dequeueReusableCell(
                withIdentifier: "UserCell", for: indexPath)
            cell.textLabel?.text = user.name
            return cell
        }
    }
    
    func update(with users: [User]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, User>()
        snapshot.appendSections([.main])
        snapshot.appendItems(users)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

ModernUserAdapter с DiffableDataSource автоматически анимирует изменения при обновлении данных: элементы появляются, исчезают или перемещаются с плавными анимациями. iOS DiffableDataSource и Android ListAdapter решают одну задачу разными API, но по одной логике — diff-расчёт + автоматическая анимация.

Object Adapter vs Class Adapter

Object Adapter использует композицию: адаптер содержит ссылку на объект Adaptee и делегирует ему вызовы. Class Adapter использует наследование: адаптер наследует Adaptee и реализует целевой интерфейс одновременно. В Java, Swift и Kotlin Class Adapter невозможен из-за отсутствия множественного наследования — остаётся только Object Adapter.

ХарактеристикаObject AdapterClass Adapter
МеханизмКомпозиция (содержит Adaptee)Наследование (extends Adaptee)
ГибкостьВыше — Adaptee можно менять в runtimeНиже — Adaptee фиксирован на этапе компиляции
СвязанностьСлабая (через интерфейс)Сильная (через наследование)
Адаптация подклассовДа — Adaptee может быть любым подклассомНет — только конкретный суперкласс
ДоступностьJava, Kotlin, Swift, C++, C#Только языки с множественным наследованием (C++)

В мобильной разработке используется только Object Adapter. RecyclerView.Adapter содержит ссылки на данные и LayoutInflater, UITableViewDataSource содержит массив элементов. Композиция делает код более гибким и тестируемым по сравнению с наследованием.

Примеры Adapter в реальных проектах

Adapter применяется не только для списков. Рассмотрим три сценария из практики мобильной разработки, где паттерн решает задачу интеграции несовместимых компонентов.

СценарийAdapteeTargetAdapter
Интеграция старой auth-библиотекиLegacyAuthLib (callback-based)AuthService (suspend)LegacyAuthAdapter
Адаптация JSON под новую модельGsonParserJsonParser (Kotlinx Serialization)GsonAdapter
Единый интерфейс для push-сервисовFCM, APNs, Huawei PushPushService token registerPushServiceAdapter

Каждый Adapter инкапсулирует логику преобразования: GsonAdapter переводит JSON-строку в объект через @SerializedName, PushServiceAdapter абстрагирует регистрацию токена для разных платформ. Клиент (бизнес-логика) работает с единым интерфейсом и не зависит от конкретной реализации.

Типичные ошибки при реализации Adapter

Ошибки в реализации Adapter приводят к тормозам списков, утечкам памяти и багам с обновлением данных. Разберём три частые проблемы.

Тяжёлые операции в onBindViewHolder

onBindViewHolder вызывается для каждой ячейки при скролле. Создание объектов, парсинг JSON или вызов БД в этом методе приводят к дёрганью списка (frame drops). Решение: все тяжёлые вычисления проводить заранее, передавая в адаптер уже готовые данные. Для изображений использовать библиотеки с кэшированием — Glide, Coil, Kingfisher.

Игнорирование DiffUtil / DiffableDataSource

Вызов notifyDataSetChanged() при каждом обновлении перерисовывает весь список, вызывая мерцание и потерю фокуса на вводах. Решение: использовать ListAdapter с DiffUtil в Android или DiffableDataSource в iOS. Diff-расчёт занимает <1 мс для списков до 1000 элементов и обеспечивает плавные анимации.

Хранение тяжёлых объектов в адаптере

Адаптер живет столько же, сколько RecyclerView/UITableView. Хранение Bitmap или больших массивов в адаптере приводит к утечкам памяти при смене конфигурации (поворот экрана, смена темы). Решение: адаптер должен хранить только лёгкие модели данных (data class / struct), а тяжёлые ресурсы загружать через ViewHolder по требованию.

Часто задаваемые вопросы

В чём разница между Adapter и Proxy?

Adapter преобразует интерфейс Adaptee к интерфейсу Target — клиент получает новый способ взаимодействия. Proxy предоставляет тот же интерфейс, что и оригинальный объект, но добавляет контроль доступа, кэширование или ленивую инициализацию. Proxy не меняет интерфейс, Adapter — меняет.

Почему RecyclerView.Adapter — это пример паттерна Adapter?

RecyclerView.Adapter преобразует данные (например, список User) в ViewHolder, который RecyclerView может отобразить. RecyclerView ожидает ViewHolder, данные имеют формат List<User> — Adapter адаптирует одно к другому. Дополнительно Adapter управляет жизненным циклом ViewHolder через recycle-пул, повышая производительность списка.

В чём разница между Object Adapter и Class Adapter?

Object Adapter использует композицию: содержит ссылку на Adaptee и делегирует ему вызовы. Class Adapter использует множественное наследование: наследует Adaptee и реализует Target. В Java, Kotlin и Swift доступен только Object Adapter из-за отсутствия множественного наследования классов. Class Adapter возможен только в C++.

Когда использовать Adapter вместо Facade?

Adapter используется, когда нужно заставить два существующих класса работать вместе, преобразуя интерфейс одного из них. Facade — когда нужно упростить взаимодействие со сложной подсистемой, предоставляя простой интерфейс. Adapter — для совместимости, Facade — для упрощения.

Можно ли реализовать Adapter без интерфейса?

Да, с помощью замыканий. Вместо интерфейса с одним методом можно передать функцию. Например, в Swift: let adapter = { (data: Data) -> [CellConfig] in /* преобразование */ }. Такой подход называется functional adapter. Однако для 2+ методов протокол/интерфейс остаётся предпочтительным — он делает контракт явным.

Итоги

  • Adapter — структурный паттерн, преобразующий несовместимый интерфейс Adaptee в Target через класс-прослойку
  • RecyclerView.Adapter (Android) и UITableViewDataSource (iOS) — главные примеры паттерна в мобильной разработке
  • Object Adapter использует композицию; Class Adapter (только C++) — множественное наследование
  • ListAdapter + DiffUtil (Android) и DiffableDataSource (iOS) — современные эволюции с автоматическим diff и анимацией
  • Тяжёлые операции в onBindViewHolder, игнорирование DiffUtil и хранение ресурсов в адаптере — частые ошибки
  • Adapter применяется для интеграции legacy-кода, адаптации форматов и унификации разных реализаций
  • Рекомендация: используйте ListAdapter/DiffableDataSource вместо ручного управления обновлениями списка

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также