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 κινεί αυτόματα τις αλλαγές κατά την ενημέρωση δεδομένων: τα στοιχεία εμφανίζονται, εξαφανίζονται ή μετακινούνται με ομαλές κινήσεις. Το DiffableDataSource του iOS και το ListAdapter του Android λύνουν την ίδια εργασία με διαφορετικά 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
Ενσωμάτωση παλιάς βιβλιοθήκης authLegacyAuthLib (callback-based)AuthService (suspend)LegacyAuthAdapter
Προσαρμογή JSON σε νέο μοντέλοGsonParserJsonParser (Kotlinx Serialization)GsonAdapter
Ενιαία διεπαφή για υπηρεσίες pushFCM, APNs, Huawei PushPushService token registerPushServiceAdapter

Κάθε Adapter ενσωματώνει τη λογική μετασχηματισμού: το GsonAdapter μεταφράζει μια συμβολοσειρά JSON σε αντικείμενο μέσω @SerializedName, το PushServiceAdapter αφαιρεί την εγγραφή token για διαφορετικές πλατφόρμες. Ο πελάτης (επιχειρηματική λογική) εργάζεται με ενιαία διεπαφή και δεν εξαρτάται από συγκεκριμένη υλοποίηση.

Συνηθισμένα λάθη κατά την υλοποίηση 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 εφαρμόζεται για ενσωμάτωση παλαιού κώδικα, προσαρμογή μορφών και ενοποίηση διαφορετικών υλοποιήσεων
  • Σύσταση: χρησιμοποιήστε ListAdapter/DiffableDataSource αντί για χειροκίνητη διαχείριση ενημερώσεων λίστας

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης