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

نمط Singleton

جزء من قسم Object Oriented Programming في رحلة Dart على Coddy — الدرس 90 من 110.

يضمن نمط Singleton أن الـ class لديه مثيل واحد فقط طوال تطبيقك بالكامل، ويوفر نقطة وصول عالمية إليه. هذا مفيد عندما تكون هناك حاجة إلى كائن واحد فقط لتنسيق الإجراءات عبر النظام - مثل مدير الإعدادات، أو اتصال قاعدة البيانات، أو خدمة تسجيل الأحداث.

في Dart، يمكنك تنفيذ نمط Singleton باستخدام مُنشئ مصنع (factory constructor) مدمج مع مثيل ثابت خاص (private static instance):

class AppConfig {
  static final AppConfig _instance = AppConfig._internal();
  
  String appName = 'MyApp';
  String version = '1.0.0';
  
  // منشئ مسمى خاص
  AppConfig._internal();
  
  // منشئ المصنع (Factory constructor) يعيد نفس المثيل
  factory AppConfig() {
    return _instance;
  }
}

void main() {
  var config1 = AppConfig();
  var config2 = AppConfig();
  
  config1.appName = 'UpdatedApp';
  
  print(config2.appName);  // UpdatedApp
  print(identical(config1, config2));  // true
}

يمنع المنشئ الخاص _internal() عمليات الإنشاء الخارجية. يحمل الحقل الساكن _instance النسخة الوحيدة، التي يتم إنشاؤها مرة واحدة عند تحميل الفئة (class). كل استدعاء لـ AppConfig() يعيد نفس هذه النسخة، لذا فإن التغييرات التي يتم إجراؤها من خلال مرجع واحد تكون مرئية في كل مكان.

استخدم Singletons باعتدال - فهي تُدخل حالة عامة (global state)، مما قد يجعل الاختبار أكثر صعوبة. وهي الأنسب للموارد المشتركة حقاً التي تحتاج إلى حالة متسقة عبر تطبيقك.

challenge icon

التحدي

سهل

لنقم ببناء مدير إعدادات اللعبة باستخدام نمط Singleton! ستقوم بإنشاء نظام يضمن وجود مثيل واحد فقط لإعدادات اللعبة في جميع أنحاء التطبيق، بحيث تشترك جميع أجزاء لعبتك في نفس التكوين.

ستقوم بتنظيم الكود الخاص بك في ملفين:

  • game_settings.dart: قم بإنشاء فئة GameSettings التي تطبق نمط Singleton. يجب أن يحتوي مدير الإعدادات الخاص بك على:
    • مثيل ثابت نهائي خاص (private static final instance) يحمل كائن GameSettings الوحيد
    • منشئ مسمى خاص (private named constructor) لمنع الإنشاء الخارجي
    • منشئ مصنع (factory constructor) يعيد دائمًا نفس المثيل
    • ثلاثة حقول للإعدادات: difficulty (String، القيمة الافتراضية: "Normal")، و soundEnabled (bool، القيمة الافتراضية: true)، و playerName (String، القيمة الافتراضية: "Player1")
    • طريقة displaySettings() تقوم بطباعة الإعدادات الحالية بهذا التنسيق: Settings: [playerName] | Difficulty: [difficulty] | Sound: [on/off] (اطبع "on" إذا كان soundEnabled هو true، و "off" إذا كان false)
  • main.dart: قم باستيراد إعدادات اللعبة الخاصة بك وأثبت أن Singleton يعمل بشكل صحيح:
    • احصل على مثيل الإعدادات وقم بتسميته settings1
    • استدعِ displaySettings() لإظهار القيم الافتراضية
    • قم بتعديل الإعدادات: غير playerName إلى "Hero"، و difficulty إلى "Hard"، و soundEnabled إلى false
    • احصل على مرجع آخر للإعدادات وقم بتسميته settings2
    • استدعِ displaySettings() على settings2 لإثبات أنه يعكس التغييرات التي تم إجراؤها من خلال settings1
    • اطبع ما إذا كان settings1 و settings2 متطابقين باستخدام: Same instance: [true/false]

يوضح هذا الفائدة الأساسية لنمط Singletons - التغييرات التي يتم إجراؤها في أي مكان في تطبيقك تظهر على الفور في كل مكان آخر، لأنه لا يوجد سوى مثيل واحد فقط!

المخرجات المتوقعة:

Settings: Player1 | Difficulty: Normal | Sound: on
Settings: Hero | Difficulty: Hard | Sound: off
Same instance: true

ورقة مرجعية

يضمن نمط Singleton أن يكون للفئة (class) مثيل واحد فقط طوال تطبيقك، ويوفر نقطة وصول عالمية إليه.

في لغة Dart، قم بتنفيذ Singleton باستخدام منشئ مصنع (factory constructor) مدمج مع مثيل ثابت خاص (private static instance):

class AppConfig {
  static final AppConfig _instance = AppConfig._internal();
  
  String appName = 'MyApp';
  String version = '1.0.0';
  
  // Private named constructor
  AppConfig._internal();
  
  // Factory constructor returns the same instance
  factory AppConfig() {
    return _instance;
  }
}

المكونات الرئيسية:

  • static final _instance: يحتفظ بالمثيل الوحيد، ويتم إنشاؤه مرة واحدة عند تحميل الفئة
  • _internal(): المنشئ الخاص يمنع إنشاء مثيلات خارجية
  • factory AppConfig(): يعيد نفس المثيل في كل مرة

مثال على الاستخدام:

var config1 = AppConfig();
var config2 = AppConfig();

config1.appName = 'UpdatedApp';

print(config2.appName);  // UpdatedApp
print(identical(config1, config2));  // true

التغييرات التي يتم إجراؤها من خلال مرجع واحد تكون مرئية في كل مكان لأن جميع المراجع تشير إلى نفس المثيل.

جرّب بنفسك

import 'game_settings.dart';

void main() {
  // TODO: settings1 واحفظه باسم settings احصل على مثيل الإعدادات 
  
  // TODO: لعرض القيم الافتراضية displaySettings() استدعِ الدالة 
  
  // TODO: قم بتعديل الإعدادات:
  // - "Hero" إلى playerName قم بتغيير 
  // - "Hard" إلى difficulty قم بتغيير 
  // - false إلى soundEnabled قم بتغيير 
  
  // TODO: settings2 واحفظه باسم settings احصل على مرجع آخر للإعدادات 
  
  // TODO: على settings2 لإثبات أنها تعكس التغييرات displaySettings() استدعِ الدالة 
  
  // TODO: متطابقان أم لا settings2 و settings1 اطبع ما إذا كان 
  // Format: Same instance: [true/false] :التنسيق
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming