Mixin مقابل الواجهة (Interface)
جزء من قسم Object Oriented Programming في رحلة Dart على Coddy — الدرس 55 من 110.
لقد رأيت كيف تُقارن الـ mixins بالوراثة (inheritance). الآن دعنا نقارن الـ mixins بالواجهات (interfaces)، بما أن كليهما يسمحان لك بالعمل مع أنواع متعددة. الفرق الجوهري هو ما يقدمانه: الواجهات (interfaces) تُحدد عقداً (contract)، بينما توفر الـ mixins تنفيذاً (implementation).
عندما تقوم بتنفيذ interface، فإنك تلتزم بتوفير methods معينة، ولكنك تكتب الكود بالكامل بنفسك. أما عندما تستخدم mixin، فإنك تحصل على الكود الفعلي مجانًا.
// نهج الواجهة (Interface) - يجب عليك تنفيذ كل شيء
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // أنت تكتب هذا
}
}
// نهج الـ Mixin - التنفيذ متضمن
mixin Printable {
void printInfo() {
print('Printing...'); // مكتوب مسبقاً لك
}
}
class Report with Printable {} // يحصل على printInfo() تلقائياًإذًا متى يجب عليك استخدام كل منهما؟ استخدم interface عندما تحتاج classes مختلفة إلى نفس method signatures ولكن بتنفيذات مختلفة. استخدم mixin عندما يجب أن تتشارك الـ classes نفس السلوك تمامًا.
| الميزة | الواجهة (Interface) | Mixin |
|---|---|---|
| يوفر | عقد فقط | عقد + كود |
| التنفيذ | تكتبه أنت | متضمن |
| الكلمة المفتاحية | implements | with |
| الأفضل لـ | سلوكيات مختلفة، نفس واجهة البرمجة (API) | سلوك مشترك |
يمكنك حتى الجمع بين كلا النهجين - استخدم الواجهات (interfaces) لتحديد ما يمكن للفئة (class) القيام به، والمخلوطات (mixins) لتوفير عمليات تنفيذ مشتركة عند الاقتضاء. في لغة Dart، عند الجمع بين كليهما، ضع دائمًا with قبل implements:
class MyClass with SomeMixin implements SomeInterface {
// تأتي with قبل implements
}التحدي
سهللنقم ببناء نظام إشعارات يوضح متى يجب استخدام الواجهات (interfaces) مقابل الـ mixins. ستقوم بإنشاء واجهة للإشعارات التي تتطلب تطبيقات مختلفة، و mixin لسلوك التسجيل (logging) المشترك الذي يجب أن يكون متطابقاً عبر جميع أنواع الإشعارات.
ستقوم بتنظيم الكود الخاص بك في ثلاثة ملفات:
notifiable.dart: قم بتعريف فئة مجردة (abstract class) باسمNotifiableتعمل كواجهة. يجب أن تعلن عن طريقتين مجردتين:send(String message)وgetChannel()التي تعيدString. ستنفذ أنواع الإشعارات المختلفة هذه الطرق بشكل مختلف بناءً على قناتها الخاصة.logging.dart: أنشئ mixin باسمLoggingيوفر سلوكاً مشتركاً يجب أن تمتلكه جميع الإشعارات. قم بتضمين طريقةlogSent(String channel, String message)التي تطبع[LOG] Sent via [channel]: [message]وطريقةlogTimestamp()التي تطبع[LOG] Timestamp recorded. هذا السلوك متطابق لجميع أنواع الإشعارات، مما يجعله مثالياً للـ mixin.main.dart: قم باستيراد كلا الملفين وإنشاء فئتين للإشعارات تدمجان الواجهة مع الـ mixin:- فئة
EmailNotificationتستخدم الـ mixin المسمىLoggingوتنفذNotifiable. يجب أن تعيدgetChannel()الخاصة بها'Email'، ويجب أن تطبع طريقةsend()الخاصة بهاSending email: [message]، ثم تستدعيlogSent()مع القناة والرسالة، ثم تستدعيlogTimestamp(). - فئة
SMSNotificationتستخدم الـ mixin المسمىLoggingوتنفذNotifiable. يجب أن تعيدgetChannel()الخاصة بها'SMS'، ويجب أن تطبع طريقةsend()الخاصة بهاSending SMS: [message]، ثم تستدعيlogSent()مع القناة والرسالة، ثم تستدعيlogTimestamp().
EmailNotificationواستدعِsend('Hello World')، واطبع سطراً فارغاً، ثم أنشئSMSNotificationواستدعِsend('Quick update').- فئة
لاحظ كيف تجبر الواجهة كل فئة على كتابة تطبيقات send() و getChannel() الخاصة بها (سلوك مختلف)، بينما يوفر الـ mixin طرق التسجيل التي تعمل بشكل متطابق لكليهما (سلوك مشترك).
المخرجات المتوقعة:
Sending email: Hello World
[LOG] Sent via Email: Hello World
[LOG] Timestamp recorded
Sending SMS: Quick update
[LOG] Sent via SMS: Quick update
[LOG] Timestamp recordedورقة مرجعية
تُحدد الواجهات (Interfaces) عقداً (تواقيع الدوال)، بينما توفر الـ mixins تنفيذاً (الكود الفعلي).
عندما تقوم بتنفيذ واجهة (interface)، يجب عليك كتابة الكود بالكامل بنفسك:
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // تكتب هذا بنفسك
}
}عندما تستخدم mixin، تحصل على التنفيذ مجاناً:
mixin Printable {
void printInfo() {
print('Printing...'); // مكتوب مسبقاً لك
}
}
class Report with Printable {} // يحصل على printInfo() تلقائياًمتى تستخدم كل منهما:
- استخدم الواجهة (interface) عندما تحتاج فئات (classes) مختلفة إلى نفس تواقيع الدوال ولكن بتنفيذات مختلفة
- استخدم الـ mixin عندما يجب أن تتشارك الفئات نفس السلوك تماماً
| الميزة | الواجهة (Interface) | Mixin |
|---|---|---|
| توفر | العقد فقط | العقد + الكود |
| التنفيذ | تكتبه بنفسك | متضمن |
| الكلمة المفتاحية | implements | with |
| الأفضل لـ | سلوكيات مختلفة، نفس الـ API | السلوك المشترك |
يمكنك الجمع بين كلا النهجين. عند الجمع، ضع دائماً with قبل implements:
class MyClass with SomeMixin implements SomeInterface {
// تأتي with قبل implements
}جرّب بنفسك
import 'notifiable.dart';
import 'logging.dart';
// TODO: إنشاء فئة EmailNotification التي:
// - تستخدم Logging mixin (عبر with)
// - تنفذ Notifiable (عبر implements)
// - تعيد getChannel() القيمة 'Email'
// - تقوم send() بطباعة 'Sending email: [message]'، ثم تستدعي logSent()، ثم logTimestamp()
// TODO: إنشاء فئة SMSNotification التي:
// - تستخدم Logging mixin (عبر with)
// - تنفذ Notifiable (عبر implements)
// - تعيد getChannel() القيمة 'SMS'
// - تقوم send() بطباعة 'Sending SMS: [message]'، ثم تستدعي logSent()، ثم logTimestamp()
void main() {
// TODO: إنشاء EmailNotification واستدعاء send('Hello World')
// TODO: طباعة سطر فارغ
// TODO: إنشاء SMSNotification واستدعاء send('Quick update')
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةالمكتبات والـ Importsمقدمة في الـ OOPالـ Classes مقابل الـ Objectsالكلمة المفتاحية thisالـ Methodsمتغيرات الـ Instanceأساسيات الـ Constructorمراجعة - آلة حاسبة بسيطة4أمان الـ Null
مقدمة في أمان الـ NullNullable مقابل Non-Nullableعاملا الـ ? و !الكلمة المفتاحية Late وأمان الـ Nullالعوامل المدركة للـ Nullأمان الـ Null في الـ Classesمراجعة - نظام ملف تعريف المستخدم7الفئات المجردة والواجهات
الفئات المجردةالدوال المجردةالواجهات في Dartالواجهات الضمنيةالتنفيذ مقابل التوسيعالواجهات المتعددةمراجعة - حاسبة الأشكال10المجموعات والأنماط العامة
نظرة عامة على List و Set و Mapالمجموعات آمنة النوعالفئات العامةالدوال العامةقيود الأنماط العامةIterable و Iteratorملخص - التخزين العام13مفاهيم OOP المتقدمة
التركيب مقابل الوراثة (Composition vs Inheritance)دوال التوسعة (Extension Methods)الأصناف القابلة للاستدعاء (Callable Classes)الأصناف المختومة (Sealed Classes - Dart 3)السجلات (Records - Dart 3)الأنماط والمطابقة (Patterns & Matching - 3.0)التعدادات مع الدوال (Enums with Methods)2المنشئات في Dart
المنشئ الافتراضيالمنشئات المسماةقوائم التهيئةالمنشئات الثابتةالمنشئات المصنعيةالمنشئات الموجهةملخص - بناء الأشكال5التغليف (Encapsulation)
الأعضاء العامة مقابل الخاصةاتفاقية البادئة _الخصوصية على مستوى المكتبةتعمق في Getters و Settersإخفاء المعلوماتمراجعة - سجلات الطلاب8Mixins
مقدمة إلى Mixinsإنشاء Mixinsاستخدام Mixins متعددةالكلمة المفتاحية on في MixinsMixin مقابل الوراثة (Inheritance)Mixin مقابل الواجهة (Interface)مراجعة - نظام الحيوانات (Animal System)3خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticحقول Final و Constمتغيرات Lateالـ Static Methods والحقولدوال الـ Getters والـ Settersمراجعة - مدير الحساب البنكي6الوراثة
أساسيات الوراثةالكلمة المفتاحية superإعادة تعريف الدوال (Method Overriding)الوسم @overrideالكلمة المفتاحية final Classالدوال البانية والوراثةمراجعة - هيكلية الموظفين9تعدد الأشكال
أساسيات تعدد الأشكالتعدد الأشكال عبر الـ Interfacesالتحقق من النوع أثناء التشغيلالمعاملات is و asالكلمة المفتاحية Covariantملخص - معالج الدفع12البرمجة الكائنية غير المتزامنة (Async OOP)
Futures و async/awaitأساسيات الـ Streamsمتحكمات الـ Streamsالـ Constructors غير المتزامنةالعمليات غير المتزامنة في دوال الكلاسمراجعة - جالب البيانات (Data Fetcher)15أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع