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 را میتوان در runtime تغییر داد | کمتر — Adaptee در زمان کامپایل ثابت است |
| وابستگی | ضعیف (از طریق رابط) | قوی (از طریق وراثت) |
| تطبیق زیرکلاسها | بله — Adaptee میتواند هر زیرکلاسی باشد | خیر — فقط سوپرکلاس مشخص |
| دسترسی | Java, Kotlin, Swift, C++, C# | فقط زبانهای با وراثت چندگانه (C++) |
در توسعه موبایل فقط Object Adapter استفاده میشود. RecyclerView.Adapter ارجاعاتی به دادهها و LayoutInflater دارد، UITableViewDataSource آرایهای از عناصر دارد. ترکیب کد را نسبت به وراثت انعطافپذیرتر و قابل تستتر میکند.
Adapter نه تنها برای لیستها استفاده میشود. سه سناریو از تمرین توسعه موبایل را بررسی میکنیم که در آن الگو مشکل یکپارچهسازی مؤلفههای ناسازگار را حل میکند.
| سناریو | Adaptee | Target | Adapter |
|---|---|---|---|
| یکپارچهسازی کتابخانه احراز هویت قدیمی | 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 ثبت توکن را برای پلتفرمهای مختلف انتزاع میکند. مشتری (منطق کسبوکار) با یک رابط یکپارچه کار میکند و به پیادهسازی خاص وابسته نیست.
اشتباهات در پیادهسازی Adapter منجر به کندی لیستها، نشت حافظه و باگهای بهروزرسانی داده میشود. سه مشکل رایج را بررسی میکنیم.
onBindViewHolder برای هر سلول هنگام اسکرول فراخوانی میشود. ایجاد اشیاء، تجزیه JSON یا پرسوجوی پایگاه داده در این متد منجر به لرزش لیست (frame drops) میشود. راهحل: تمام محاسبات سنگین را از قبل انجام دهید، دادههای آماده را به آداپتر منتقل کنید. برای تصاویر از کتابخانههای ذخیرهسازی استفاده کنید — Glide، Coil، Kingfisher.
فراخوانی notifyDataSetChanged() در هر بهروزرسانی کل لیست را بازنویسی میکند و باعث سوسو زدن و از دست رفتن فوکس در ورودیها میشود. راهحل: از ListAdapter با DiffUtil در Android یا DiffableDataSource در iOS استفاده کنید. محاسبه تفاوت برای لیستهای تا 1000 عنصر <1 ms طول میکشد و انیمیشنهای روان را تضمین میکند.
آداپتر به اندازه 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 از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.