Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

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
يوفرعقد فقطعقد + كود
التنفيذتكتبه أنتمتضمن
الكلمة المفتاحيةimplementswith
الأفضل لـسلوكيات مختلفة، نفس واجهة البرمجة (API)سلوك مشترك

يمكنك حتى الجمع بين كلا النهجين - استخدم الواجهات (interfaces) لتحديد ما يمكن للفئة (class) القيام به، والمخلوطات (mixins) لتوفير عمليات تنفيذ مشتركة عند الاقتضاء. في لغة Dart، عند الجمع بين كليهما، ضع دائمًا with قبل implements:

class MyClass with SomeMixin implements SomeInterface {
  // تأتي with قبل implements
}
challenge icon

التحدي

سهل

لنقم ببناء نظام إشعارات يوضح متى يجب استخدام الواجهات (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
توفرالعقد فقطالعقد + الكود
التنفيذتكتبه بنفسكمتضمن
الكلمة المفتاحيةimplementswith
الأفضل لـسلوكيات مختلفة، نفس الـ 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')
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming