Adapter — είναι ένα δομικό πρότυπο σχεδίασης που μετατρέπει τη διεπαφή μιας κλάσης σε άλλη διεπαφή που αναμένει ο πελάτης. Στην κινητή ανάπτυξη, το Adapter χρησιμοποιείται συχνότερα στο RecyclerView.Adapter για Android και στο UITableViewDataSource για iOS. Σύμφωνα με δεδομένα του Google I/O (2024), το RecyclerView χρησιμοποιείται στο 95% των εφαρμογών Android για εμφάνιση λιστών, και κάθε μία απαιτεί τη δική της υλοποίηση Adapter.
Κύρια σημεία
Adapter — δομικό πρότυπο που λύνει το πρόβλημα ασυμβατότητας διεπαφών. Τυλίγει ένα αντικείμενο (Adaptee) σε μια κλάση (Adapter) με τη διεπαφή που αναμένει ο πελάτης (Target). Ο πελάτης εργάζεται με το Target, χωρίς να γνωρίζει την ύπαρξη του Adaptee. Αυτή είναι μια παραλλαγή του προτύπου Wrapper.
// Υπάρχουσα κλάση με ασύμβατη διεπαφή
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 δεκαετίας. Αυτό επιτρέπει την αντικατάσταση υλοποιήσεων χωρίς αλλαγή του κώδικα πελάτη.
RecyclerView.Adapter — η πιο διαδεδομένη υλοποίηση του προτύπου Adapter στο Android. Μετατρέπει δεδομένα (λίστα αντικειμένων) σε ViewHolder που το RecyclerView εμφανίζει στην οθόνη. Με την εμφάνιση του ListAdapter (Android Architecture Components), το πρότυπο απέκτησε ενσωματωμένη υποστήριξη υπολογισμού διαφορών για κινούμενη εικόνα αλλαγών.
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().
UITableViewDataSource — είναι ταυτόχρονα και το πρότυπο Adapter και DataSource. Μετατρέπει τα δεδομένα μοντέλου σε κελιά πίνακα. Από το iOS 13, η Apple εισήγαγε το DiffableDataSource — μια σύγχρονη αντικατάσταση που, όπως το ListAdapter στο Android, υπολογίζει αυτόματα αλλαγές και κινεί τις ενημερώσεις.
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 χρησιμοποιεί σύνθεση: ο προσαρμογέας περιέχει αναφορά στο αντικείμενο Adaptee και του αναθέτει κλήσεις. Class Adapter χρησιμοποιεί κληρονομικότητα: ο προσαρμογέας κληρονομεί το Adaptee και ταυτόχρονα υλοποιεί τη διεπαφή-στόχο. Σε Java, Swift και Kotlin, το Class Adapter είναι αδύνατο λόγω έλλειψης πολλαπλής κληρονομικότητας — μένει μόνο το Object Adapter.
| Χαρακτηριστικό | Object Adapter | Class Adapter |
|---|---|---|
| Μηχανισμός | Σύνθεση (περιέχει Adaptee) | Κληρονομικότητα (extends Adaptee) |
| Ευελιξία | Υψηλότερη — το Adaptee μπορεί να αλλάξει κατά τον χρόνο εκτέλεσης | Χαμηλότερη — το Adaptee είναι σταθερό κατά τη μεταγλώττιση |
| Σύζευξη | Ασθενής (μέσω διεπαφής) | Ισχυρή (μέσω κληρονομικότητας) |
| Προσαρμογή υποκλάσεων | Ναι — το Adaptee μπορεί να είναι οποιαδήποτε υποκλάση | Όχι — μόνο συγκεκριμένη υπερκλάση |
| Διαθεσιμότητα | Java, Kotlin, Swift, C++, C# | Μόνο γλώσσες με πολλαπλή κληρονομικότητα (C++) |
Στην κινητή ανάπτυξη χρησιμοποιείται μόνο Object Adapter. Το RecyclerView.Adapter περιέχει αναφορές σε δεδομένα και LayoutInflater, το UITableViewDataSource περιέχει έναν πίνακα στοιχείων. Η σύνθεση κάνει τον κώδικα πιο ευέλικτο και δοκιμάσιμο σε σύγκριση με την κληρονομικότητα.
Adapter εφαρμόζεται όχι μόνο για λίστες. Ας εξετάσουμε τρία σενάρια από την πρακτική κινητής ανάπτυξης όπου το πρότυπο λύνει το πρόβλημα ενσωμάτωσης ασύμβατων στοιχείων.
| Σενάριο | Adaptee | Target | Adapter |
|---|---|---|---|
| Ενσωμάτωση παλιάς βιβλιοθήκης auth | LegacyAuthLib (callback-based) | AuthService (suspend) | LegacyAuthAdapter |
| Προσαρμογή JSON σε νέο μοντέλο | GsonParser | JsonParser (Kotlinx Serialization) | GsonAdapter |
| Ενιαία διεπαφή για υπηρεσίες push | FCM, APNs, Huawei Push | PushService token register | PushServiceAdapter |
Κάθε Adapter ενσωματώνει τη λογική μετασχηματισμού: το GsonAdapter μεταφράζει μια συμβολοσειρά JSON σε αντικείμενο μέσω @SerializedName, το PushServiceAdapter αφαιρεί την εγγραφή token για διαφορετικές πλατφόρμες. Ο πελάτης (επιχειρηματική λογική) εργάζεται με ενιαία διεπαφή και δεν εξαρτάται από συγκεκριμένη υλοποίηση.
Λάθη στην υλοποίηση Adapter οδηγούν σε επιβράδυνση λιστών, διαρροές μνήμης και σφάλματα ενημέρωσης δεδομένων. Ας εξετάσουμε τρία συνηθισμένα προβλήματα.
Το onBindViewHolder καλείται για κάθε κελί κατά την κύλιση. Δημιουργία αντικειμένων, ανάλυση JSON ή ερωτήματα βάσης δεδομένων σε αυτή τη μέθοδο οδηγούν σε τραυλισμό της λίστας (frame drops). Λύση: εκτελέστε όλους τους βαρείς υπολογισμούς εκ των προτέρων, μεταφέροντας έτοιμα δεδομένα στον προσαρμογέα. Για εικόνες, χρησιμοποιήστε βιβλιοθήκες με προσωρινή αποθήκευση — Glide, Coil, Kingfisher.
Η κλήση notifyDataSetChanged() σε κάθε ενημέρωση επανασχεδιάζει ολόκληρη τη λίστα, προκαλώντας τρεμόπαιγμα και απώλεια εστίασης στα πεδία εισαγωγής. Λύση: χρησιμοποιήστε ListAdapter με DiffUtil στο Android ή DiffableDataSource στο iOS. Ο υπολογισμός διαφοράς διαρκεί <1 ms για λίστες έως 1000 στοιχεία και εξασφαλίζει ομαλές κινήσεις.
Ο προσαρμογέας ζει όσο το RecyclerView/UITableView. Αποθήκευση Bitmap ή μεγάλων πινάκων στον προσαρμογέα οδηγεί σε διαρροές μνήμης κατά την αλλαγή διαμόρφωσης (περιστροφή οθόνης, αλλαγή θέματος). Λύση: ο προσαρμογέας πρέπει να αποθηκεύει μόνο ελαφρά μοντέλα δεδομένων (data class / struct) και να φορτώνει βαρείς πόρους μέσω ViewHolder κατ' απαίτηση.
Συχνές Ερωτήσεις
Adapter μετατρέπει τη διεπαφή του Adaptee στη διεπαφή Target — ο πελάτης αποκτά νέο τρόπο αλληλεπίδρασης. Proxy παρέχει την ίδια διεπαφή με το αρχικό αντικείμενο, αλλά προσθέτει έλεγχο πρόσβασης, προσωρινή αποθήκευση ή τεμπέλικη αρχικοποίηση. Το Proxy δεν αλλάζει τη διεπαφή, το Adapter — αλλάζει.
RecyclerView.Adapter μετατρέπει δεδομένα (π.χ. λίστα User) σε ViewHolder που μπορεί να εμφανίσει το RecyclerView. Το RecyclerView αναμένει ViewHolder, τα δεδομένα είναι σε μορφή List<User> — ο Adapter προσαρμόζει το ένα στο άλλο. Επιπλέον, ο Adapter διαχειρίζεται τον κύκλο ζωής του ViewHolder μέσω της δεξαμενής ανακύκλωσης, βελτιώνοντας την απόδοση της λίστας.
Object Adapter χρησιμοποιεί σύνθεση: περιέχει αναφορά στο Adaptee και του αναθέτει κλήσεις. Class Adapter χρησιμοποιεί πολλαπλή κληρονομικότητα: κληρονομεί το Adaptee και υλοποιεί το Target. Σε Java, Kotlin και Swift, μόνο το Object Adapter είναι διαθέσιμο λόγω έλλειψης πολλαπλής κληρονομικότητας κλάσεων. Το Class Adapter είναι δυνατό μόνο σε C++.
Adapter χρησιμοποιείται όταν δύο υπάρχουσες κλάσεις πρέπει να συνεργαστούν μετασχηματίζοντας τη διεπαφή μιας από αυτές. Facade — όταν η αλληλεπίδραση με ένα πολύπλοκο υποσύστημα πρέπει να απλοποιηθεί παρέχοντας μια απλή διεπαφή. Adapter — για συμβατότητα, Facade — για απλοποίηση.
Ναι, με closures. Αντί για διεπαφή με μία μέθοδο, μπορεί να μεταδοθεί μια συνάρτηση. Για παράδειγμα, σε Swift: let adapter = { (data: Data) -> [CellConfig] in /* μετασχηματισμός */ }. Αυτή η προσέγγιση ονομάζεται functional adapter. Ωστόσο για 2+ μεθόδους, το πρωτόκολλο/διεπαφή παραμένει προτιμότερο — καθιστά τη σύμβαση ρητή.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης