January 19, 2026 (7mo ago) — last updated July 12, 2026 (1mo ago)

دليل Singleton في TypeScript — أفضل الممارسات

دليل عملي لنمط Singleton في TypeScript: متى يستخدم، كيف تنفّذه بأمان، بدائل DI، ونصائح لإعادة الهيكلة والاختبار.

← Back to blog
Cover Image for دليل Singleton في TypeScript — أفضل الممارسات

استكشف نمط Singleton في TypeScript: لماذا يُستخدم، كيف تُنفّذه بأمان، ومتى تفضّل بدائل مثل حقن التبعيات لتحسين الاختبارية والصيانة.

إتقان نمط Singleton في TypeScript: دليل كامل

رسم بقلم رصاص لكاتب ملكي متوج، يفحص بعناية لفة متوهجة على طاولة.

في عالم تطوير البرمجيات، هناك أدوات قوية لكنها تتطلب حكمة في الاستخدام. نمط Singleton واحد من هذه الأدوات: هدفه ضمان وجود نسخة واحدة فقط من فئة وتوفير نقطة وصول عالمية لها.1

فكر فيه ككاتب ملكي مركزي لسجل واحد من الإعلانات الرسمية—يمنع التكرار ويضمن الاتساق. عند تطبيقه بعناية، يوفر Singleton إدارة مركزية لموارد فريدة مثل إعدادات التطبيق أو تجمعات الاتصالات. مع ذلك، يجب موازنة الراحة مع قابلية الاختبار والصيانة.

ما هو نمط Singleton ومتى يكون ملائماً؟

نمط Singleton يقيّد إنشاء الفئة بحيث لا يمكن وجود سوى مثيل واحد طوال دورة حياة التطبيق. هذه النسخة تصبح مصدر الحقيقة لمهمة محددة ويمكن الوصول إليها من أي مكان في الشيفرة.

أمثلة مناسبة:

  • خدمات التسجيل (Logging) التي تحتاج إلى ملف أو تيار موحّد.
  • إدارة الإعدادات المركزية لتجنّب تعارض القيم عبر الوحدات.
  • واجهات الأجهزة حيث يجب أن تكون هناك نقطة تحكم واحدة للأوامر.

السياق مهم: لا تستخدم Singleton كبديل لهيكل تصميم واضح أو عندما تحتاج المرونة والاختبارية العالية.

لمحة سريعة عن السمات الأساسية

السمةالوصف
نسخة واحدةتُصمم الفئة لامتلاك مثيل واحد فقط غالباً عبر منشئ خاص.
نقطة وصول عالميةطريقة ثابتة مثل getInstance() للوصول إلى المثيل.
تهيئة كسولةيتم إنشاء المثيل عند الحاجة لتقليل تكلفة بدء التشغيل.
إدارة الحالةمكان مركزي لحالة مشتركة مثل إعدادات التطبيق.

فوائد ومخاطر Singleton

ميزان يقارن أنماط التصميم المنظمة والقابلة للملاحظة مع شفرة معقدة ومتشابكة وتجريبية.

فوائد

  • نقطة وصول موحّدة وسهلة الاستخدام عبر الوحدات.
  • حفظ الموارد عند تطبيق التهيئة الكسولة.
  • تقليل تكرار إنشاء كائنات باهظة الثمن.

مخاطر

  • ربط وثيق يخبئ التبعيات داخل الشيفرة.
  • حالة عالمية قابلة للتغيير قد تؤدي لأخطاء يصعب تعقّبها.
  • صعوبة في الاختبار ومحاكاة التبعيات.

عموماً، استخدم Singleton للخدمات الفريدة فعلاً، وفضّل الحقن الصريح للتبعيات عندما تحتاج إلى اختبارية ومرونة أعلى.3

تأثير Singleton على الاختبار والربط

الاعتماد على حالة عالمية يجعل اختبارات الوحدة هشة: قد تتسرب الحالة بين اختبارات منفصلة، وتصبح المحاكاة أصعب. لذلك، الفرق الحديثة تميل إلى اعتماد حقن التبعيات (DI) لجعل التبعيات صريحة وقابلة للاستبدال أثناء الاختبار.3

تنفيذ Singleton آمن نوعيًا في TypeScript

لننتقل إلى مثال عملي وآمن نوعيًا: ConfigManager لإدارة إعدادات التطبيق.

class ConfigManager {
  private static instance: ConfigManager;
  private settings: Map<string, any> = new Map();

  private constructor() {
    console.log("Initializing ConfigManager instance...");
    this.settings.set("API_URL", "https://api.example.com");
    this.settings.set("TIMEOUT", 5000);
  }

  public static getInstance(): ConfigManager {
    if (!ConfigManager.instance) {
      ConfigManager.instance = new ConfigManager();
    }
    return ConfigManager.instance;
  }

  public get(key: string): any {
    return this.settings.get(key);
  }
}

هذا التنفيذ يستخدم منشئًا خاصًا وخصيصة ثابتة static لفرض وجود نسخة واحدة وتهيئة كسولة. مناسب لاحتياجات بسيطة ومتوقعة.2

مثال استخدام عملي

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}...`);
  }
}

console.log("Application starting...");
const service1 = new ApiService();
service1.fetchData();
const service2 = new ApiService();
console.log("Application finished.");

عند التشغيل، سترى رسالة تهيئة ConfigManager مرة واحدة فقط، ما يثبت مشاركة نفس النسخة.

لماذا يعتبر البعض Singleton مشكلة؟

السبب الرئيسي أن Singleton يخفي تبعيات داخلية وحالة عالمية قابلة للتغيير. هذا يجعل فهم الشيفرة واختبارها أصعب، وقد يسبب أخطاء تعتمد على ترتيب تنفيذ الاختبارات أو توقيتات التزامن.3

التزامن والحالات السباقية

Singleton ذي حالة يمكن أن يسبب تعارضات في سيناريوهات متزامنة؛ مثلاً عدّاد جلسات قد يُحدّثه طلبان في نفس الوقت دون آليات تزامن مناسبة.

بدائل عملية: حقن التبعيات وحاويات IoC

حقن التبعيات يجعل التبعيات صريحة عبر المنشئات أو الخصائص، ما يحسّن الاختبارية والمرونة. قارن هذا المثال المبسّط:

interface IConfigManager {
  get(key: string): any;
}

class ApiService {
  private apiUrl: string;

  constructor(config: IConfigManager) {
    this.apiUrl = config.get("API_URL");
  }
}

الآن ApiService لا تعتمد على حالة عالمية؛ أثناء الاختبارات يمكنك تمرير وهمي بسرعة.

أطر مثل NestJS وAngular توفر حاويات DI مدمجة، بينما مكتبات عامة مثل InversifyJS تمنح تحكمًا أكبر في دورة حياة الكائنات.45

استخدام حاوية IoC يتيح لك اختيار دورة الحياة: مؤقتة، نطاقية، أو شبه-Singleton مع إمكانيات اختبارية أفضل.

إعادة هيكلة Singletons في قاعدة شيفرة قديمة

اقترح نهجاً تدريجياً دون تعطيل المنتج:

  1. حدد جميع نقاط استدعاء getInstance() وارسم حدود المسؤوليات.
  2. عرّف واجهة تُجرد السلوك المطلوب.
  3. أعد هيكلة مستهلك واحد ليقبل الواجهة عبر المنشئ.
  4. مرّر النسخة الحالية من Singleton من جذر التطبيق أثناء المرحلة الانتقالية، ثم ادفع نحو استخدام حاوية DI أو إنشاء نسخة مُدارة.

هذا النهج يحافظ على الاستقرار أثناء تحسين التصميم.

نصائح عملية وسريعة

  • استخدم Singleton فقط للخدمات التي يجب أن تكون فريدة فعلاً.
  • فضّل DI وواجهات واضحة لجعل التبعيات مرئية وقابلة للاختبار.
  • اجعل Singleton كسول التهيئة وكن واعياً للتزامن وسلامة الخيوط.
  • عند ترحيل الأنظمة القديمة، أدرج تغييرات تدريجية مع اختبارات متزايدة.

أسئلة شائعة — أسئلة وأجوبة سريعة

Q: متى أختار Singleton؟ A: عندما يكون المورد فريدًا بطبيعته—مثل مسجل مركزي أو واجهة جهاز—وحيث تكون تبعات الحالة العالمية مقبولة.

Q: كيف أختبر شيفرة تعتمد على Singleton؟ A: قدم واجهة للمسؤولية، أعد هيكلة المستهلكين لقبولها عبر المنشئ، وحقن بدائل اختبارية تدريجياً.

Q: ما البديل الأكثر مرونة؟ A: حقن التبعيات عبر حاوية IoC؛ يوفّر تحكماً أفضل في دورة الحياة وقابلية اختبار أعلى.4

أسئلة وأجوبة موجزة إضافية

س: هل Singleton وStatic class نفس الشيء؟ ج: لا. الفئة الثابتة تحتوي على أعضاء ثابتة فقط، أما Singleton فيمتلك مثيلاً حقيقياً يمكنه تنفيذ واجهات وتمريره ككائن.

س: كيف أتجنب مشاكل التزامن مع Singleton؟ ج: استخدم آليات تزامن مناسبة أو اجعل الحالة غير قابلة للتغيير أو انقل مسؤولية إدارة الحالة إلى مكونات نطاقية أو مُدارة بواسطة حاوية DI.

س: ما أول خطوة لإزالة Singleton من مشروع قديم؟ ج: ارسم استخداماته، عرّف واجهات، وأعد هيكلة مستهلك واحد ليقبل التبعية، ثم درّج التغييرات عبر التطبيق.


1.
Martin Fowler, “Singleton,” Bliki, https://martinfowler.com/bliki/Singleton.html
2.
3.
Mark Seemann, “Singletons Are Pathological Liars,” https://blog.ploeh.dk/2010/07/28/SingletonsArePathologicalLiar/
4.
NestJS Documentation, “Dependency Injection,” https://docs.nestjs.com/fundamentals/injection
5.
InversifyJS Documentation, https://inversify.io/
6.
State of JS 2021 — TypeScript usage and adoption overview, https://2021.stateofjs.com/en-US/languages/typescript/
← Back to blog
🙋🏻‍♂️

الذكاء الاصطناعي يكتب الكود.
أنت تجعله يدوم.

في عصر تسريع الذكاء الاصطناعي، الكود النظيف ليس مجرد ممارسة جيدة — إنه الفرق بين الأنظمة التي تتوسع وقواعد الكود التي تنهار تحت وزنها.