Adapter — cos'è, il pattern di trasformazione delle interfacce per lo sviluppo mobile

Autore: IT Sectr Pubblicato: 2026-02-18 Tempo di lettura: 10 min

Adapter è un pattern di progettazione strutturale che converte l'interfaccia di una classe in un'altra interfaccia attesa dal client. Nello sviluppo mobile, Adapter è utilizzato più frequentemente in RecyclerView.Adapter per Android e UITableViewDataSource per iOS. Secondo Google I/O (2024), RecyclerView è utilizzato nel 95% delle app Android per visualizzare elenchi, e ciascuna richiede la propria implementazione di Adapter.

Punti chiave

  • Adapter è un pattern strutturale che consente a oggetti con interfacce incompatibili di lavorare insieme
  • RecyclerView.Adapter è un'implementazione del pattern in Android, che converte dati in ViewHolder per l'elenco
  • UITableViewDataSource è un'implementazione in iOS, che combina i pattern Adapter e DataSource
  • Object Adapter usa la composizione: l'adattatore contiene un riferimento all'oggetto adattato
  • Class Adapter usa l'ereditarietà multipla — possibile solo in C++, non disponibile in Java/Swift/Kotlin

Cos'è il pattern Adapter?

Adapter è un pattern strutturale che risolve il problema dell'incompatibilità delle interfacce. Incapsula un oggetto (Adaptee) in una classe (Adapter) con l'interfaccia attesa dal client (Target). Il client lavora con Target senza conoscere l'esistenza di Adaptee. È una variante del pattern Wrapper.

Kotlin
// Classe esistente con interfaccia incompatibile
class LegacyAuthApi {
    fun loginWithToken(token: String): Map {
        return mapOf("status" to "ok", "user_id" to 42)
    }
}

// Interfaccia target (cosa si aspetta il client)
interface AuthService {
    suspend fun login(credentials: Credentials): Result
}

// Adapter — trasforma LegacyAuthApi in 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 converte la chiamata login(credentials) in loginWithToken(token) per la vecchia API. Il client (ViewModel) lavora attraverso l'interfaccia AuthService e non sa se sotto c'è il più recente Firebase Auth o un'API legacy di dieci anni fa. Questo consente di sostituire le implementazioni senza modificare il codice client.

Adapter in Android: RecyclerView e ListAdapter

RecyclerView.Adapter è l'implementazione più comune del pattern Adapter in Android. Converte i dati (un elenco di oggetti) in ViewHolder che RecyclerView visualizza sullo schermo. Con l'introduzione di ListAdapter (Android Architecture Components), il pattern ha ottenuto il supporto integrato per il calcolo delle differenze (diff) per animare i cambiamenti.

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 usa ListAdapter con DiffUtil per un ridisegno efficiente dell'elenco. Quando i dati vengono aggiornati, DiffCallback calcola la differenza tra l'elenco vecchio e quello nuovo, e RecyclerView anima solo gli elementi modificati — senza ridisegnare l'intero elenco. Questo è un vantaggio chiave rispetto al vecchio ListView, che richiedeva notifyDataSetChanged() manuale.

Adapter in iOS: UITableView e DiffableDataSource

UITableViewDataSource è contemporaneamente il pattern Adapter e il pattern DataSource. Converte i dati del modello in celle della tabella. Da iOS 13, Apple ha introdotto DiffableDataSource — un sostituto moderno che, come ListAdapter in Android, calcola automaticamente i cambiamenti e anima gli aggiornamenti.

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
    }
}

// Versione moderna: 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 con DiffableDataSource anima automaticamente i cambiamenti quando i dati vengono aggiornati: gli elementi appaiono, scompaiono o si muovono con animazioni fluide. DiffableDataSource di iOS e ListAdapter di Android risolvono lo stesso problema con API diverse ma la stessa logica — calcolo delle differenze + animazione automatica.

Object Adapter vs Class Adapter

Object Adapter usa la composizione: l'adattatore contiene un riferimento all'oggetto Adaptee e gli delega le chiamate. Class Adapter usa l'ereditarietà: l'adattatore eredita da Adaptee e implementa contemporaneamente l'interfaccia target. In Java, Swift e Kotlin, Class Adapter è impossibile a causa della mancanza di ereditarietà multipla — rimane solo Object Adapter.

CaratteristicaObject AdapterClass Adapter
MeccanismoComposizione (contiene Adaptee)Ereditarietà (estende Adaptee)
FlessibilitàMaggiore — Adaptee può essere cambiato a runtimeMinore — Adaptee è fisso in fase di compilazione
AccoppiamentoDebole (tramite interfaccia)Forte (tramite ereditarietà)
Adattamento sottoclassiSì — Adaptee può essere qualsiasi sottoclasseNo — solo una superclasse specifica
DisponibilitàJava, Kotlin, Swift, C++, C#Solo linguaggi con ereditarietà multipla (C++)

Nello sviluppo mobile, viene utilizzato solo Object Adapter. RecyclerView.Adapter contiene riferimenti ai dati e a LayoutInflater, UITableViewDataSource contiene un array di elementi. La composizione rende il codice più flessibile e testabile rispetto all'ereditarietà.

Esempi di Adapter in progetti reali

Adapter non è utilizzato solo per gli elenchi. Consideriamo tre scenari dalla pratica dello sviluppo mobile in cui il pattern risolve il problema dell'integrazione di componenti incompatibili.

ScenarioAdapteeTargetAdapter
Integrazione di libreria auth legacyLegacyAuthLib (basata su callback)AuthService (suspend)LegacyAuthAdapter
Adattamento JSON a un nuovo modelloGsonParserJsonParser (Kotlinx Serialization)GsonAdapter
Interfaccia unificata per servizi pushFCM, APNs, Huawei PushPushService token registerPushServiceAdapter

Ogni Adapter incapsula la logica di conversione: GsonAdapter traduce una stringa JSON in un oggetto tramite @SerializedName, PushServiceAdapter astrae la registrazione del token per diverse piattaforme. Il client (logica di business) lavora con un'interfaccia unificata e non dipende dall'implementazione specifica.

Errori comuni nell'implementazione di Adapter

Gli errori nell'implementazione di Adapter portano a lentezza degli elenchi, perdite di memoria e bug con gli aggiornamenti dei dati. Esaminiamo tre problemi comuni.

Operazioni pesanti in onBindViewHolder

onBindViewHolder viene chiamato per ogni cella durante lo scorrimento. Creare oggetti, analizzare JSON o chiamare il database in questo metodo porta a cadute di frame (frame drops). Soluzione: eseguire tutti i calcoli pesanti in anticipo, passando dati già pronti all'adattatore. Utilizzare librerie di caching per le immagini — Glide, Coil, Kingfisher.

Ignorare DiffUtil / DiffableDataSource

Chiamare notifyDataSetChanged() a ogni aggiornamento ridisegna l'intero elenco, causando sfarfallio e perdita di focus sugli input. Soluzione: utilizzare ListAdapter con DiffUtil in Android o DiffableDataSource in iOS. Il calcolo delle differenze richiede <1 ms per elenchi fino a 1000 elementi e garantisce animazioni fluide.

Conservare oggetti pesanti nell'adattatore

L'adattatore vive quanto RecyclerView/UITableView. Conservare Bitmap o grandi array nell'adattatore porta a perdite di memoria in caso di cambiamenti di configurazione (rotazione dello schermo, cambio tema). Soluzione: l'adattatore dovrebbe conservare solo modelli di dati leggeri (data class / struct) e caricare risorse pesanti tramite ViewHolder su richiesta.

Domande frequenti

Qual è la differenza tra Adapter e Proxy?

Adapter converte l'interfaccia di Adaptee nell'interfaccia di Target — il client ottiene un nuovo modo di interazione. Proxy fornisce la stessa interfaccia dell'oggetto originale, ma aggiunge controllo degli accessi, caching o inizializzazione pigra. Proxy non cambia l'interfaccia, Adapter sì.

Perché RecyclerView.Adapter è un esempio del pattern Adapter?

RecyclerView.Adapter converte i dati (ad esempio, un elenco di User) in un ViewHolder che RecyclerView può visualizzare. RecyclerView si aspetta un ViewHolder, i dati sono in formato List<User> — l'Adapter adatta l'uno all'altro. Inoltre, l'Adapter gestisce il ciclo di vita dei ViewHolder attraverso il pool di riciclo, migliorando le prestazioni dell'elenco.

Qual è la differenza tra Object Adapter e Class Adapter?

Object Adapter usa la composizione: contiene un riferimento ad Adaptee e gli delega le chiamate. Class Adapter usa l'ereditarietà multipla: eredita da Adaptee e implementa Target. In Java, Kotlin e Swift, è disponibile solo Object Adapter a causa della mancanza di ereditarietà multipla delle classi. Class Adapter è possibile solo in C++.

Quando usare Adapter invece di Facade?

Adapter si usa quando è necessario far funzionare insieme due classi esistenti convertendo l'interfaccia di una di esse. Facade si usa quando è necessario semplificare l'interazione con un sottosistema complesso fornendo un'interfaccia semplice. Adapter è per la compatibilità, Facade per la semplificazione.

Si può implementare Adapter senza interfaccia?

Sì, usando le closure. Invece di un'interfaccia con un metodo, si può passare una funzione. Ad esempio, in Swift: let adapter = { (data: Data) -> [CellConfig] in /* conversione */ }. Questo approccio si chiama adattatore funzionale. Tuttavia, per 2+ metodi, un protocollo/interfaccia rimane preferibile — rende il contratto esplicito.

Riepilogo

  • Adapter è un pattern strutturale che converte un'interfaccia Adaptee incompatibile in Target tramite una classe intermediaria
  • RecyclerView.Adapter (Android) e UITableViewDataSource (iOS) sono i principali esempi del pattern nello sviluppo mobile
  • Object Adapter usa la composizione; Class Adapter (solo C++) usa l'ereditarietà multipla
  • ListAdapter + DiffUtil (Android) e DiffableDataSource (iOS) sono evoluzioni moderne con diff automatico e animazione
  • Operazioni pesanti in onBindViewHolder, ignorare DiffUtil e conservare risorse nell'adattatore sono errori comuni
  • Adapter è utilizzato per integrare codice legacy, adattare formati e unificare diverse implementazioni
  • Raccomandazione: utilizzare ListAdapter/DiffableDataSource invece della gestione manuale degli aggiornamenti dell'elenco

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche