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 се може мењати у 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-у. Израчунавање разлика траје <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 /* претварање */ }. Овај приступ се назива 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. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође