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، يحول البيانات إلى ViewHolders للقائمة
  • 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 أو واجهة قديمة من عشر سنوات. هذا يسمح باستبدال التطبيقات دون تغيير كود العميل.

Adapter في Android: RecyclerView وListAdapter

RecyclerView.Adapter هو التطبيق الأكثر شيوعًا لنمط Adapter في Android. يحول البيانات (قائمة كائنات) إلى ViewHolders التي يعرضها RecyclerView على الشاشة. مع ظهور ListAdapter (Android Architecture Components)، حصل النمط على دعم مدمج لحساب الفروقات (diff) لتحريك التغييرات.

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 يحلان نفس المشكلة بواجهات برمجة مختلفة ولكن بنفس المنطق — حساب الفروقات + الرسوم المتحركة التلقائية.

Object Adapter مقابل Class Adapter

Object Adapter يستخدم التركيب: المحول يحتوي على مرجع لكائن Adaptee ويفوض الاستدعاءات إليه. Class Adapter يستخدم الوراثة: يرث المحول من Adaptee وينفذ الواجهة الهدف في نفس الوقت. في Java وSwift وKotlin، Class Adapter غير ممكن بسبب عدم وجود وراثة متعددة — يبقى فقط Object Adapter.

الخاصيةObject AdapterClass Adapter
الآليةالتركيب (يحتوي على Adaptee)الوراثة (يمدد Adaptee)
المرونةأعلى — يمكن تغيير Adaptee في وقت التشغيلأقل — Adaptee ثابت في وقت الترجمة
الاقترانضعيف (عبر واجهة)قوي (عبر الوراثة)
تكييف الفئات الفرعيةنعم — Adaptee يمكن أن يكون أي فئة فرعيةلا — فقط فئة أصل محددة
التوفرJava, Kotlin, Swift, C++, C#لغات ذات وراثة متعددة فقط (C++)

في تطوير التطبيقات المحمولة، يُستخدم Object Adapter فقط. RecyclerView.Adapter يحتوي على مراجع للبيانات وLayoutInflater، UITableViewDataSource يحتوي على مصفوفة عناصر. التركيب يجعل الكود أكثر مرونة وقابلية للاختبار مقارنة بالوراثة.

أمثلة على Adapter في المشاريع الحقيقية

Adapter لا يُستخدم فقط للقوائم. دعنا ننظر في ثلاثة سيناريوهات من ممارسة تطوير التطبيقات المحمولة حيث يحل النمط مشكلة دمج المكونات غير المتوافقة.

السيناريوAdapteeTargetAdapter
دمج مكتبة مصادقة قديمةLegacyAuthLib (قائم على الاستدعاء)AuthService (suspend)LegacyAuthAdapter
تكييف JSON مع نموذج جديدGsonParserJsonParser (Kotlinx Serialization)GsonAdapter
واجهة موحدة لخدمات الدفع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 مللي ثانية للقوائم حتى 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 بدون واجهة؟

نعم، باستخدام الإغلاقات. بدلاً من واجهة بطريقة واحدة، يمكن تمرير دالة. على سبيل المثال، في Swift: let adapter = { (data: Data) -> [CellConfig] in /* تحويل */ }. يُسمى هذا النهج المحول الوظيفي. ومع ذلك، للطرق 2+، يبقى البروتوكول/الواجهة مفضلًا — فهو يجعل العقد صريحًا.

الخلاصة

  • Adapter هو نمط هيكلي يحول واجهة Adaptee غير المتوافقة إلى Target عبر فئة وسيطة
  • RecyclerView.Adapter (Android) وUITableViewDataSource (iOS) هما المثالان الرئيسيان للنمط في تطوير التطبيقات المحمولة
  • Object Adapter يستخدم التركيب؛ Class Adapter (C++ فقط) يستخدم الوراثة المتعددة
  • ListAdapter + DiffUtil (Android) وDiffableDataSource (iOS) هما تطوران حديثان مع فرق تلقائي ورسوم متحركة
  • العمليات الثقيلة في onBindViewHolder وتجاهل DiffUtil وتخزين الموارد في المحول هي أخطاء شائعة
  • Adapter يُستخدم لدمج الكود القديم وتكييف التنسيقات وتوحيد التطبيقات المختلفة
  • توصية: استخدم ListAdapter/DiffableDataSource بدلاً من الإدارة اليدوية لتحديثات القائمة

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا