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 или десет години старо API. Това позволява замяна на реализации без промяна на клиентския код.

Adapter в Android: RecyclerView и ListAdapter

RecyclerView.Adapter — най-разпространената реализация на шаблона Adapter в Android. Той преобразува данни (списък от обекти) в ViewHolder-и, които RecyclerView показва на екрана. С появата на ListAdapter (Android Architecture Components) шаблонът получи вградена поддръжка за изчисляване на разлики за анимация на промени.

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, но с една и съща логика — изчисляване на разлики + автоматична анимация.

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 може да се променя по време на изпълнениеПо-ниска — 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. Изчисляването на разлики отнема <1 ms за списъци до 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 чрез рециклиращ пул, повишавайки производителността на списъка.

Каква е разликата между 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 без интерфейс?

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

Обобщение

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

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

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също