نمط Singleton
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 65 من 87.
إن نمط Singleton هو نمط تصميم إنشائي يضمن أن يكون للفئة مثيل واحد فقط طوال تطبيقك، مع توفير نقطة وصول عامة إلى ذلك المثيل. وهذا مفيد للموارد التي ينبغي مشاركتها، مثل مديري التكوين، أو تجمعات الاتصالات، أو خدمات تسجيل الأحداث.
يعمل هذا النمط بجعل constructor خاصًا، مما يمنع direct instantiation. وبدلًا من ذلك، تتحكم دالة static في الوصول إلى instance الوحيدة:
public class DatabaseConnection {
private static DatabaseConnection instance;
// المنشئ الخاص يمنع الإنشاء المباشر
private DatabaseConnection() {
System.out.println("Connection created");
}
// نقطة وصول عالمية
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public void query(String sql) {
System.out.println("Executing: " + sql);
}
}كل استدعاء للدالة getInstance() يعيد الكائن نفسه. يتم إنشاء instance فقط عند طلبه لأول مرة. يُسمّى ذلك lazy initialization:
DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();
System.out.println(db1 == db2); // true - same instanceالمكوّنات الأساسية لـ Singleton هي: حقل private static يحتفظ بـ instance، وconstructor من النوع private، وmethod من النوع public static تُعيد instance. يضمن هذا المزيج أنك تحصل دائمًا على object نفسه، بغضّ النظر عن عدد المرات التي تطلب فيها object.
التحدي
سهللنَبْنِ مدير إعدادات لتطبيق باستخدام نمط Singleton! سيضمن مدير الإعدادات لديك أن تشارك جميع أجزاء تطبيقك الإعدادات نفسها، بغضّ النظر عن عدد مرات طلبها access.
ستنظّم شيفرتك عبر ملفين:
AppConfig.java: أنشئ فئة Singleton تدير إعدادات تكوين التطبيق. يجب أن تحتويAppConfigلديك على:حقل static خاص للاحتفاظ بالـ instance الوحيد.
حقل String خاص باسم
appNameوحقل int خاص باسمmaxUsers.constructor خاص يهيّئ
appNameإلى"MyApplication"وmaxUsersإلى100، ثم يطبعAppConfig initialized.دالة static عامة
getInstance()تنشئ الـ instance فقط إذا لم يكن موجودًا بعد (lazy initialization)، ثم تعيده.دالة
setAppName(String name)تحدّث اسم التطبيق.دالة
setMaxUsers(int max)تحدّث الحد الأقصى للمستخدمين.دالة
displaySettings()تطبعApp: [appName], Max Users: [maxUsers].Main.java: أثبت أن Singleton يعمل بصورة صحيحة! ستتلقى inputين: اسم تطبيق جديد (String) وقيمة جديدة للحد الأقصى للمستخدمين (عدد صحيح).أولًا، احصل على instance من
AppConfigوخزّنه في متغير باسمconfig1. استدعِdisplaySettings()لعرض القيم الافتراضية.ثم حدّث الإعدادات باستخدام قيم input الخاصة بك، وذلك باستدعاء
setAppNameوsetMaxUsersعلىconfig1.احصل على instance آخر وخزّنه في
config2. استدعِdisplaySettings()علىconfig2لإثبات أنه يعكس التغييرات التي أُجريت من خلالconfig1.أخيرًا، اطبع ما إذا كان كلا المرجعين يشيران إلى الكائن نفسه:
Same instance: [true/false]، وذلك بمقارنةconfig1 == config2.
ستتلقى inputين بالترتيب: اسم التطبيق الجديد (String) والحد الأقصى الجديد للمستخدمين (عدد صحيح).
يوضح هذا التحدي الفائدة الأساسية من نمط Singleton: بغضّ النظر عن عدد مرات طلبك للإعدادات، ستحصل دائمًا على الـ instance نفسه بالحالة المشتركة نفسها!
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// قراءة المدخلات
String newAppName = scanner.nextLine();
int newMaxUsers = scanner.nextInt();
// TODO: الحصول على أول مثيل من AppConfig وتخزينه في config1
// TODO: استدعاء displaySettings() على config1 لعرض القيم الافتراضية
// TODO: تحديث الإعدادات باستخدام setAppName و setMaxUsers على config1
// TODO: الحصول على مثيل آخر وتخزينه في config2
// TODO: استدعاء displaySettings() على config2 لإثبات مشاركة التغييرات
// TODO: طباعة ما إذا كان كلا المرجعين يشيران إلى الكائن نفسه:
// "Same instance: [true/false]" using config1 == config2
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت