Facade هو نمط تصميم هيكلي يوفر واجهة مبسّطة لنظام فرعي معقد من الفئات. في تطوير التطبيقات المتنقلة، يُنفَّذ Facade غالباً كـ Service Layer أو UseCase، مخفياً التفاعل مع الشبكة وقاعدة البيانات والتحليلات. وفقاً لـ Martin Fowler (Patterns of Enterprise Application Architecture, 2003)، يعد Facade من الأنماط الرئيسية لتنظيم طبقة الخدمات.
الأساسيات
Facade نمط هيكلي يوفر واجهة موحّدة لمجموعة من واجهات النظام الفرعي. يحدد واجهة عالية المستوى تبسّط استخدام النظام الفرعي. Facade لا يضيف وظائف جديدة — بل ينظّم المكونات الموجودة، مخفياً تعقيد تفاعلها عن العميل.
// نظام فرعي معقد
class AuthApi {
suspend fun login(email: String, pass: String): TokenResponse
}
class UserDao {
suspend fun saveUser(user: UserEntity)
suspend fun getUser(id: Long): UserEntity?
}
class AnalyticsTracker {
fun track(event: String, params: Map )
}
// Facade — واجهة بسيطة للواجهة UI
class AuthService(
private val api: AuthApi,
private val dao: UserDao,
private val analytics: AnalyticsTracker
) {
suspend fun loginUser(email: String, password: String): Result {
return runCatching {
val token = api.login(email, password)
val user = User(token.userId, email, token.accessToken)
dao.saveUser(user.toEntity())
analytics.track("login_success", mapOf("method" to "email"))
user
}
}
}AuthService هو Facade يخفي AuthApi وUserDao وAnalyticsTracker عن ViewModel. يستدعي UI دالة loginUser(email, password) بدلاً من ثلاثة طلبات منفصلة إلى API وقاعدة البيانات والتحليلات. هذا يقلل الاقتران: إذا أصبح AuthApi غداً FirebaseAuth أو انتقل UserDao إلى Room، فلن يتغير سوى Facade وليس UI.
Service Layer تطبيق شائع لـ Facade في التطبيقات المتنقلة. يغلّف منطق الأعمال والتنسيق بين الطبقات. في Android، يُنفَّذ Service Layer غالباً عبر UseCase (Clean Architecture)، وفي iOS عبر بروتوكولات Manager أو Service.
| المكوّن | الدور في النظام الفرعي | ما يخفيه Facade |
|---|---|---|
| AuthApi | طلب شبكة إلى الخادم | تنسيق الطلب ونقطة النهاية ومعالجة أخطاء HTTP |
| UserDao | التخزين المحلي للرمز المميّز | مخطط قاعدة البيانات واستعلامات SQL والترحيلات |
| AnalyticsTracker | إرسال أحداث التحليلات | Firebase/AppMetrica SDK وتنسيق الأحداث |
| NetworkMonitor | التحقق من توفر الشبكة | ConnectivityManager وBroadcastReceiver |
AuthService يجمع المكونات الأربعة جميعها. يستدعي ViewModel طريقة واحدة دون معرفة أنه خلف الكواليس يحدث طلب شبكة وكتابة في قاعدة البيانات وتتبع وفحص للشبكة. عند الاختبار، يمكن استبدال AuthService بـ mock للتحقق من منطق المصادقة بالكامل دون التكامل مع المكونات الحقيقية.
Facade وAdapter وMediator أنماط هيكلية، لكنها تحل مشكلات مختلفة. غالباً ما يُخلط بينها لأن الثلاثة تُدخل كائناً وسيطاً. لنحلل الاختلافات بمثال تطبيق متنقل.
| الجانب | Facade | Adapter | Mediator |
|---|---|---|---|
| الهدف | تبسيط واجهة النظام الفرعي | تحويل واجهة | تقليل الاقتران بين المكونات |
| الاتجاه | واجهة واحدة ← النظام الفرعي | العميل ← Adaptee | N مكون ↔ Mediator |
| تغيير الواجهة | ينشئ واجهة جديدة مبسّطة | يحوّل الواجهة الموجودة | لا يغيّرها، بل ينسّق |
| هل يعرف النظام الفرعي النمط؟ | لا | لا | نعم، يتواصل عبر Mediator |
| مثال في التطوير المتنقل | UseCase / Service Layer | RecyclerView.Adapter | Coordinator في iOS |
Facade لا يخفي النظام الفرعي — يمكن للعميل الوصول إلى AuthApi مباشرة عند الحاجة. Adapter يغيّر واجهة Adaptee إلزامياً. Mediator ينسّق التفاعلات المعقدة بين كائنات عديدة قد لا تعرف بعضها البعض.
تطبيق Facade بلغة Kotlin لنظام Android مع Clean Architecture يستخدم UseCase كنقطة دخول لكل سيناريو أعمال. UseCase هو Facade يخفي المستودع والمنظّم (mapper) والتبعيات الأخرى عن طبقة UI.
// Repository هو أيضاً Facade، لكن بمستوى أدنى
class UserRepositoryImpl(
private val local: UserLocalDataSource,
private val remote: UserRemoteDataSource,
private val mapper: UserMapper
) : UserRepository {
override suspend fun getUserProfile(id: String): UserProfile {
val cached = local.getUser(id)
if (cached != null && !cached.isStale) {
return mapper.toProfile(cached)
}
val dto = remote.fetchUser(id)
val entity = mapper.toEntity(dto)
local.saveUser(entity)
return mapper.toProfile(entity)
}
}
// UseCase — Facade لسيناريو الأعمال
class LoadUserProfileUseCase(
private val repo: UserRepository,
private val analytics: AnalyticsTracker
) {
suspend operator fun invoke(userId: String): Result {
return runCatching {
val profile = repo.getUserProfile(userId)
analytics.track("profile_loaded", mapOf("user_id" to userId))
profile
}
}
}LoadUserProfileUseCase هو Facade لسيناريو تحميل الملف الشخصي. يخفي منطق التخزين المؤقت (local ← remote) وتحويل DTO ← Entity ← Profile وتتبع التحليلات. يستدعي ViewModel دالة invoke(userId) ويحصل على UserProfile جاهز أو خطأ. يمكن اختبار UseCase بشكل معزول باستبدال المستودع بكائن mock.
Facade في iOS يُنفَّذ غالباً كـ Manager أو Service. على عكس Android، يستخدم iOS البروتوكولات لتحديد واجهة Facade، ما يسمح بتبديل التطبيقات بسهولة في الاختبارات. لننظر إلى Facade للعمل مع الوسائط — التحميل والتخزين المؤقت والعرض.
protocol MediaServiceProtocol {
func loadImage(from url: URL) async -> Result<UIImage, Error>
}
final class MediaService: MediaServiceProtocol {
private let cache: ImageCache
private let downloader: ImageDownloader
private let decoder: ImageDecoder
func loadImage(from url: URL) async -> Result<UIImage, Error> {
// 1. فحص الكاش
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. تحميل البيانات
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. فك الترميز
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. الحفظ في الكاش
cache.setImage(image, for: url)
return .success(image)
}
}MediaService يغلّف عملية من ثلاث خطوات: الكاش ← التحميل ← فك الترميز. يستدعي UI طريقة واحدة loadImage(from:) بدلاً من إدارة ImageCache وURLSession وImageDecoder. عند الاختبار، يمكن استبدال MediaServiceProtocol بـ mock يعيد صوراً محددة مسبقاً دون تحميل حقيقي.
الأخطاء عند تصميم Facade تلغي مزاياه: بدلاً من التبسيط تحصل على God Object يعتمد عليه النظام بأكمله. لنحلل المشكلات الثلاث الرئيسية.
عندما يحتوي Facade واحد على طرق للمصادقة وتحميل الملف الشخصي وإرسال الرسائل والمزامنة — فهذا God Object. علامة: أكثر من 15 طريقة عامة في فئة واحدة. الحل: تقسيمه إلى عدة Facade متخصصة حسب مجالات المسؤولية — AuthService وProfileService وMessagingService.
إذا أعاد Facade أنواعاً خاصة بالنظام الفرعي (على سبيل المثال، FirebaseUser أو RealmObject)، فإن العميل يظل مرتبطاً بتطبيق محدد. الحل: يجب أن يعيد Facade أنواعه الخاصة فقط (data class / struct)، مما يجرّد العميل تماماً من تفاصيل النظام الفرعي.
عندما يمنع Facade الوصول المباشر إلى النظام الفرعي، يصبح عنق زجاجة. أحياناً يحتاج العميل طريقة محددة من النظام الفرعي، وإجباره على المرور عبر Facade أمر زائد. يجب ألا يكون Facade بوابة صارمة: يوفر واجهة مريحة لكنه لا يحجب الوصول المباشر إلى المكونات.
الأسئلة الشائعة
Facade يوفر واجهة مبسّطة للنظام الفرعي، وغالباً ما ينشئ مجموعة طرق جديدة. Proxy يحافظ على نفس واجهة الكائن الأصلي، لكنه يضيف التحكم في الوصول أو التحميل الكسول. Facade للتبسيط، وProxy للتحكم.
Service Layer هو تطبيق لنمط Facade على مستوى عمارة التطبيق. يحدد الحدود بين UI ومنطق الأعمال، مخفياً تفاصيل تنفيذ الخدمات. في Android، يُنفَّذ Service Layer غالباً عبر UseCase؛ وفي iOS عبر بروتوكولات Manager أو Service.
God Facade ينشأ عندما تتولى فئة واحدة مسؤولية عدة أنظمة فرعية غير مرتبطة. العلامات: أكثر من 15 طريقة عامة، طرق من مجالات مختلفة (المصادقة + المدفوعات + الإشعارات)، فئة يصعب اختبارها (أكثر من 10 تبعيات). الحل: تقسيمها إلى Facade مجال.
في تطبيق من 1-2 شاشة، يكون Facade زائداً — استدعاء API وقاعدة البيانات مباشرة من UI أبسط وأوضح. يؤتي Facade ثماره مع 5+ شاشات و3+ أنظمة فرعية. في المشاريع الصغيرة والمتوسطة يكفي Repository كطبقة Facade وحيدة دون غلاف UseCase إضافي.
Facade يبسّط الاختبار لأنه يستبدل النظام الفرعي بأكمله بكائن mock واحد. بدلاً من محاكاة ثلاثة مكونات (شبكة + قاعدة بيانات + تحليلات)، يكفي محاكاة Facade واحد. في Swift تُستخدم البروتوكولات لهذا الغرض، وفي Kotlin الواجهات. كما أن Facade مناسب لاختبارات التكامل التي تتحقق من تنسيق المكونات.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا