نمط 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)، مما قد يجعل الاختبار أكثر صعوبة. وهي الأنسب للموارد المشتركة حقاً التي تحتاج إلى حالة متسقة عبر تطبيقك.
التحدي
سهللنقم ببناء مدير إعدادات اللعبة باستخدام نمط 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)
- مثيل ثابت نهائي خاص (private static final instance) يحمل كائن
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] :التنسيق
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع