استكشف نمط Singleton: متى تستخدمه، أمثلة عملية بـ TypeScript، والبدائل الأساسية.
January 19, 2026 (2mo ago)
إتقان نمط Singleton: دليل كامل للمطور
استكشف نمط Singleton: متى تستخدمه، أمثلة عملية بـ TypeScript، والبدائل الأساسية.
← Back to blog
إتقان نمط Singleton في TypeScript: دليل كامل

في عالم تطوير البرمجيات، هناك أدوات قوية ولكن يجب التعامل معها بحذر. نمط Singleton واحد منها. في جوهره، إنه مفهوم بسيط: ضمان وجود نسخة واحدة فقط من فئة وتوفير طريقة عالمية واحدة للوصول إليها.1
فكر فيه كمدير إعدادات مركزي أو خدمة تسجيل مخصصة لتطبيقك بأكمله. لن ترغب في وجود عدة كائنات إعدادات متضاربة منتشرة هنا وهناك، ولا تريد أيضاً أن تكون سجلات النظام مبعثرة عبر ملفات مختلفة بواسطة مثيلات مسجّلين متنافسين. يقوم Singleton بفرض النظام بمنع هذه النسخ المكررة، موفراً الذاكرة وتجنب الفوضى. إنه نمط أساسي لإدارة الوصول إلى الموارد المشتركة.
ما هو نمط Singleton ومتى يكون مفيدًا؟
تخيل مملكة من العصور الوسطى بها كاتب ملكي رسمي واحد فقط. هذا الشخص هو الوحيد المخوّل لتدوين المراسيم الملكية. يضمن ذلك أن كل قانون وإعلان يكون متسقاً، ومخولاً بشكل صحيح، ومخزناً في سجل واحد حاسم. لو أمكن لأي شخص أن يقرر أن يصبح كاتباً، فسوف تنحدر المملكة بسرعة إلى الفوضى بسجلات متناقضة وارتباك عام.
نمط التصميم Singleton يعمل على هذا المبدأ نفسه داخل برنامجك. وظيفته الأساسية هي تقييد الفئة بحيث لا يمكن إنشاء سوى كائن واحد منها أبداً. تصبح هذه النسخة الوحيدة مصدر الحقيقة لمهمة محددة، ويمكن الوصول إليها بسهولة من أي مكان في قاعدة الشيفرة. هكذا تفرض السيطرة على الموارد التي يجب ألا تتكرر.
الغرض الأساسي والتشبيه
Singleton ليس مجرد منع الأشخاص من إنشاء كائنات جديدة؛ بل يتعلق بمركزة التحكم. تماماً مثلما يوفر الكاتب الملكي نقطة وصول واحدة لسجلات المملكة الرسمية، يوفر مثيل Singleton بوابة متاحة عالمياً إلى مورد مشترك. يمنع أجزاء مختلفة من تطبيقك من إنشاء نسخ معزولة وربما متعارضة.
مثال كلاسيكي هو تجمع اتصالات قاعدة البيانات. بالتأكيد لا تريد أن يفتح كل مكوّن في تطبيقك اتصالاً منفصلاً بقاعدة البيانات—فهذه طريقة مؤكدة لاستنزاف موارد الخادم وإبطاء الأداء حتى التوقف. بدلاً من ذلك، يمكن أن يدير Singleton تجمع اتصال واحد، ويوزعه بكفاءة عند الحاجة.
الفكرة الأساسية بسيطة لكنها قوية: فئة واحدة، نسخة واحدة، نقطة وصول عالمية واحدة. هذا الهيكل يضمن أن كل التفاعلات مع مورد محدد تمر عبر قناة واحدة ومتحكَّم بها.
لمحة عن نمط Singleton
| السمة | الوصف والمنطق |
|---|---|
| نسخة واحدة | تم تصميم الفئة لتملك نسخة واحدة فقط طوال دورة حياة التطبيق، وغالباً ما يُفرض ذلك باستخدام منشئ خاص. |
| نقطة وصول عالمية | طريقة ثابتة (مثل getInstance()) توفر وسيلة واحدة ومعروفة للوصول إلى النسخة من أي مكان في الشيفرة. |
| تهيئة كسولة | غالباً ما تُنشأ النسخة الوحيدة عند أول طلب لها، وليس عند بدء التطبيق، مما قد يحسن الأداء. |
| إدارة الحالة | تعمل كمكان مركزي لقطعة محددة من الحالة العالمية، مثل إعدادات التطبيق أو جلسة المستخدم. |
يلخّص هذا الجدول بشكل مرتب سبب وجود هذا النمط: لفرض وجود نسخة واحدة متاحة عالمياً للموارد التي هي بطبيعتها فريدة.
حالات استخدام عملية
بينما لدى نمط Singleton نصيبه من النقاد، إلا أنه ليس بلا استخدامات شرعية. يكون أكثر فعالية عندما يكون لديك مورد بطبيعته فريد داخل النظام.
فيما يلي بعض السيناريوهات حيث يكون Singleton منطقيًا:
- خدمات التسجيل (Logging): نسخة مسجل واحدة تضمن توجيه كل الأحداث إلى نفس الملف أو التدفق.
- إدارة الإعدادات: مصدر واحد لإعدادات التطبيق يتجنب التناقض عبر الوحدات.
- الوصول إلى واجهات الأجهزة: واجهة واحدة لجهاز تمنع أوامر متضاربة.
فوائد ومساوئ استخدام Singletons

يمكن أن يبدو نمط Singleton أداة موثوقة عندما تحتاج إلى نقطة وصول واحدة لمورد مشترك. يمنحك طريقة مباشرة لإدارة أشياء مثل كائن الإعدادات أو خدمة التسجيل عبر تطبيقك كله.
فوائد Singletons
- تبسيط الاستخدام عبر الوحدات بنقطة وصول عالمية.
- الحفاظ على الموارد عبر التهيئة الكسولة يمكن أن يقلل تكلفة بدء التشغيل.
- تقليل التكرار يمنع وجود نسخ متعددة متضاربة من موارد مكلفة.
مساوئ Singletons
- الربط الوثيق: قد تخفي الفئات تبعياتها باللجوء إلى الحالة العالمية.
- الحالة العالمية: الحالة المشتركة القابلة للتغيير يمكن أن تسبب أخطاء يصعب العثور عليها.
- الآثار الجانبية الخفية: الطرق التي تعتمد على Singleton لا تظهر تلك التبعية في تواقيعها، مما يصعب التفكير والاختبار.
التأثير على الاختبار والربط
تعقّد Singletons اختبار الوحدات لأنها تدخل حالة عالمية ومستديمة. هناك خطر من تسرب الحالة بين تشغيل وآخر للاختبارات، ويمكن أن يصبح محاكاة Singleton أمرًا محرجًا. تميل الفرق الحديثة إلى تفضيل حقن التبعيات لأنه يجعل التبعيات صريحة ويسهل استبدالها أثناء الاختبارات.3
موازنة المقايضة
عند اتخاذ قرار استخدام Singleton، قم بوزن الراحة مقابل قابلية الصيانة والاختبار على المدى الطويل. بالنسبة لرموز قديمة، غالبًا ما تكون عمليات إعادة الهيكلة التدريجية نحو حقن التبعيات الخيار الأكثر أمانًا: احتفظ بالسلوك أثناء تقليل الربط الخفي وتحسين الاختبارية.
Singletons يبادل البساطة بالحالة العالمية، لذا اختر بحكمة بناءً على احتياجات فريقك.
النقاط الرئيسية المستخلصة
- استخدم Singletons باعتدال وفقط للخدمات التي يجب أن تكون فريدة حقًا.
- فضّل حقن التبعيات الصريح لفصل أفضل واختبارية أعلى.
- إذا كان لا بد من استخدام Singleton، فاجعله كسول التهيئة وكن واعياً للتزامن وسلامة الخيوط.
- بالنسبة للأنظمة القديمة، قم بإخراج Singletons تدريجياً عن طريق إدخال الواجهات وDI في جذر التكوين.
كيفية تنفيذ نمط Singleton في TypeScript

لننتقل من التجريد إلى العملي ونبني Singleton حديث وآمن من حيث الأنواع في TypeScript. السر هو منشئ private وطريقة static تعمل كحارس بوابة. هذا المزيج يضمن أنه لا يمكن لأي جزء آخر من تطبيقك إنشاء نسخة جديدة؛ الجميع يمر عبر نقطة الدخول الوحيدة.2
لمثالنا العملي، سننشئ ConfigManager. تقوم هذه الفئة بتحميل وتوفير إعدادات التطبيق، مما يضمن أن كل مكوّن يقرأ من نفس مصدر الحقيقة.
بناء ConfigManager آمن نوعيًا
// A practical example of the Singleton pattern for configuration management.
class ConfigManager {
// 1. A private, static property to hold the single instance.
private static instance: ConfigManager;
// 2. A place to store our configuration data.
private settings: Map<string, any> = new Map();
// 3. The private constructor. This stops `new ConfigManager()` from working anywhere else.
private constructor() {
// In a real app, you'd load from a file, environment variables, or a service.
console.log("Initializing ConfigManager instance...");
this.settings.set("API_URL", "https://api.example.com");
this.settings.set("TIMEOUT", 5000);
}
// 4. The public, static method that controls access to the single instance.
public static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
// 5. A regular public method to get a specific setting.
public get(key: string): any {
return this.settings.get(key);
}
}
// TypeScript will stop you from doing this:
// const config = new ConfigManager(); // Error: Constructor of class 'ConfigManager' is private.
يستخدم هذا الهيكل معدلات الوصول في TypeScript لفرض فئة بنسخة واحدة وتهيئة كسولة. منشئ private ونمط النسخة الثابتة static مباشر وفعال للعديد من الاحتياجات البسيطة.
وضع Singleton في خدمة عملية
class ApiService {
private apiUrl: string;
constructor() {
const config = ConfigManager.getInstance();
this.apiUrl = config.get("API_URL");
console.log(`ApiService initialized with API URL: ${this.apiUrl}`);
}
public fetchData(): void {
console.log(`Fetching data from ${this.apiUrl}...`);
// Real data-fetching logic would live here.
}
}
// --- Application Entry Point ---
console.log("Application starting...");
const service1 = new ApiService();
service1.fetchData();
const service2 = new ApiService();
console.log("Application finished.");
عند تشغيل هذا الكود، يجب أن ترى رسالة تهيئة من ConfigManager مرة واحدة فقط، مما يثبت أن كلا الخدمتين حصلتا على نفس النسخة.
لماذا يحظى Singleton بسمعة سيئة؟
يبدو نمط Singleton جذابًا لأنه بسيط ويمنحك كائنًا متاحًا عالمياً. ما يمكن أن يخطئ هو الربط الخفي، الحالة العالمية القابلة للتغيير، وكوابيس الاختبار. عندما تصل فئة بصمت إلى مثيل عالمي، فإنها تخفي تبعية كان ينبغي أن تكون صريحة في منشئها. هذا يجعل النظام أصعب في الفهم والاختبار.3
كوابيس التزامن والحالة العالمية
يمكن أن تتسبب Singletons ذات الحالة في حالات سباق في السيناريوهات المتزامنة. تخيل SessionCounter حيث يزيد طلبان متزامننان نفس العداد. بدون تزامن، قد يقرؤوا نفس القيمة الابتدائية ويكتبوا تحديثات متعارضة. هذه الأخطاء تعتمد على التوقيت ويصعب إعادة إنتاجها.
مأزق الاختبار
يجعل Singleton اختبارات الوحدة هشة لأن الحالة يمكن أن تتسرب بين الاختبارات ويصبح التمثيل الوهمي صعبًا. قد تبدأ الاختبارات بالاعتماد على ترتيب التنفيذ، ويصبح الطقم غير مستقر. لهذا تتبنى الفرق غالبًا حقن التبعيات: فهو يجعل التبعيات صريحة ويسهل المحاكاة.
على الرغم من هذه المشاكل، تستمر Singletons في الظهور في الأكواد الفعلية. غالباً ما تنتشر داخل قاعدة الشيفرة بمجرد إدخالها، ولهذا السبب يكون التدقيق وإعادة الهيكلة التدريجية مهمين عند تحسين الهندسة المعمارية.
بدائل حديثة لنمط Singleton
بعد رؤية المخاطر التي يقدمها Singleton، ربما تتساءل: "ماذا يجب أن أستخدم بدلاً منه؟" حقن التبعيات (DI) هو النهج الشائع لإدارة الموارد المشتركة. يجعل DI التبعيات صريحة ويحسن الاختبارية والمرونة.
حقن التبعيات مقابل Singletons
قارن بين ApiService الأصلي، الذي يصل إلى Singleton، وبين نسخة تقبل مدير الإعدادات عبر منشئها.
interface IConfigManager {
get(key: string): any;
}
class ApiService {
private apiUrl: string;
constructor(config: IConfigManager) {
this.apiUrl = config.get("API_URL");
}
}
الآن ApiService تعتمد فقط على عقدة IConfigManager. أثناء الاختبارات يمكنك تمرير وهمي أو مزيف، مما يجعل الاختبارات سريعة ومتوقعة.
عن طريق عكس السيطرة على من ينشئ التبعيات، تصبح المكونات أكثر تركيزًا ومرونة. هذه الفكرة مركزية لمبدأ عكس التبعيات.
دور حاويات IoC
تدير حاوية عكس السيطرة (IoC) إنشاء الكائنات وحقنها لتطبيقك. توفر أطر TypeScript الشائعة حاويات DI مدمجة، مثل NestJS وAngular، أو مكتبات مثل InversifyJS للمشروعات العامة.45
تتيح لك الحاويات اختيار كيفية مشاركة الكائنات: عابرة، نطاقية، أو بدورات حياة تشبه Singleton. هذا يمنحك فوائد النسخة المشتركة الواحدة دون الربط الخفي لنمط Singleton البرمجي.
كيفية إعادة هيكلة Singletons في قاعدة شيفرة قديمة
اعمل بشكل تدريجي. حدد أين يُستخدم Singleton، عرّف واجهة واضحة تصف سلوكه، وابدأ بتغيير المستهلكين الفرديين لقبول الواجهة عبر حقن المنشئ. ثم اربط النسخة الملموسة في جذر التكوين أو دع حاوية DI تديرها.
الخطوة 1: تحديد وعزل Singleton
ابحث عن كل استدعاء إلى MySingleton.getInstance() وارسم حدود مسؤوليات Singleton. عرّف واجهة تُدرج الطرق العامة التي تحتاج إليها.
الخطوة 2: إدخال حقن التبعيات تدريجياً
أعد هيكلة مستهلك واحد في كل مرة:
- غيّر المنشئ ليقبل الواجهة.
- استبدل الاستدعاءات المباشرة إلى
getInstance()باستدعاءات إلى النسخة المحقونة. - في نقطة الإنشاء، مرّر نسخة Singleton حتى يكتمل الانتقال.
يُبقي هذا التطبيق مستقرًا أثناء تقليل الربط الخفي.
الخطوة 3: استبدال Singleton بنسخة مُدارة
بمجرد أن يعتمد المستهلكون على الواجهة، يمكنك إزالة getInstance() الثابتة وجعل التنفيذ فئة عادية بمنشئ عام. أنشئ نسخة واحدة في جذر التكوين ومررها حسب الحاجة، أو دع حاوية DI تتولى دورة الحياة والنطاق.
الإجابة عن أسئلتك الملحّة حول Singletons
هل Singletons فكرة سيئة دائمًا؟
ليس دائماً. يمكن أن تكون منطقية للخدمات الفريدة والحالية مثل مسجل مركزي أو محول أجهزة. حتى في هذه الحالة، يوفر DI وجذر تكوين متحكم سلوكاً مماثلاً مع قابلية اختبار أفضل.
كيف يعبث Singleton باختبار الوحدة؟
يدخل حالة عالمية ومستديمة يمكن أن تتسرب بين الاختبارات ويصعب محاكاتها. قد تصبح الاختبارات معتمدة على الترتيب وغير مستقرة. يجعل DI الاختبارات أبسط لأن المخادعات يمكن حقنها مباشرة.
أليست الفئة الثابتة Static Class هي نفس الشيء؟
لا. الفئة الثابتة تحتوي فقط على أعضاء ثابتة ولا يمكن تهيئتها. Singleton لديه نسخة حقيقية واحدة ويمكنه تنفيذ واجهات وتمريره ككائن. يمكن أن يؤدي كلا النهجين إلى ربط وثيق، لذا فضّل DI للمرونة.
الخطوات التالية لفريقك
ابدأ حوارًا حول مكان، إن وُجد، حاجة وجود نسخة مشتركة فعلية. نمذج DI في وحدة معزولة، واعتمد معايير ترميز واضحة، واستخدم أدوات لقياس الدين الفني. يساعد البرمجة الزوجية والتغذية الراجعة المستمرة في إبقاء عمليات إعادة الهيكلة آمنة وفعالة.
تذكّر، لا يوجد نمط تصميم طلقة سحرية. لـ Singletons مكانها، لكن يجب استخدامها بحكمة ومزجها بواجهات نظيفة وقواعد ملكية واضحة.
الأسئلة الشائعة — أسئلة وأجوبة سريعة
Q: متى يكون Singleton مناسبًا؟ A: عندما يكون المورد فريدًا وحالة خالية من الحالة حقًا، مثل مسجل مركزي أو محول جهاز. فضّل DI كلما أمكن.
Q: كيف أختبر الشيفرة التي تستخدم Singleton حاليًا؟ A: قدّم واجهة، أعد هيكلة المستهلكين لقبول الواجهة، وحقن مُضاعِف اختبار. قم بذلك تدريجياً لتجنب تراجعات واسعة النطاق.
Q: ما المسار الأكثر أمانًا لإزالة Singletons من تطبيق قديم؟ A: ارسم استخداماتها، عرّف واجهات، أعد هيكلة المستهلكين لقبول التبعيات، ثم أنشئ وحقن نسخة واحدة في جذر التكوين أو استخدم حاوية DI.
الذكاء الاصطناعي يكتب الكود.أنت تجعله يدوم.
في عصر تسريع الذكاء الاصطناعي، الكود النظيف ليس مجرد ممارسة جيدة — إنه الفرق بين الأنظمة التي تتوسع وقواعد الكود التي تنهار تحت وزنها.