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

نمط Singleton الآمن للخيوط (Thread-Safe)

جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 52 من 70.

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

يستخدم نمط Singleton الأساسي منشئًا خاصًا لمنع الإنشاء الخارجي وخاصية ثابتة لتوفير الوصول:

public class Logger
{
    private static Logger _instance;
    private static readonly object _lock = new object();
    
    private Logger() { }  // منشئ خاص
    
    public static Logger Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                    _instance = new Logger();
                return _instance;
            }
        }
    }
    
    public void Log(string message) => Console.WriteLine(message);
}

تضمن عبارة lock سلامة الخيوط (thread safety) - إذا حاولت خيوط متعددة الوصول إلى Instance في وقت واحد، يمكن لخيط واحد فقط الدخول إلى القسم الحرج في كل مرة. بدون ذلك، يمكن لخيطين رؤية _instance كقيمة فارغة (null) وإنشاء مثيلات منفصلة.

تقدم لغة C# نهجاً أبسط وآمناً من حيث الخيوط (thread-safe) باستخدام التهيئة الثابتة (static initialization):

public class Logger
{
    private static readonly Logger _instance = new Logger();
    
    private Logger() { }
    
    public static Logger Instance => _instance;
    
    public void Log(string message) => Console.WriteLine(message);
}

هذا الإصدار آمن من حيث تعدد الخيوط (thread-safe) لأن CLR يضمن أن تهيئة الحقول الثابتة (static field initialization) تحدث مرة واحدة فقط. يضمن كلا النهجين حصولك دائمًا على نفس المثيل (instance):

Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");
challenge icon

التحدي

سهل

لنقم ببناء مدير إعدادات آمن للخيوط (thread-safe) باستخدام نمط Singleton. في التطبيقات الحقيقية، يجب أن تكون إعدادات التكوين قابلة للوصول في جميع أنحاء البرنامج مع ضمان وجود مثيل واحد فقط - وهي حالة استخدام مثالية لنمط Singleton.

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

  • ConfigManager.cs: قم بإنشاء فئة ConfigManager في مساحة الأسماء Configuration التي تطبق نمط Singleton. يجب أن يقوم مدير الإعدادات الخاص بك بما يلي:
    • امتلاك حقل ثابت خاص (private static field) للاحتفاظ بالمثيل الوحيد
    • استخدام منشئ خاص (private constructor) لمنع إنشاء مثيلات خارجية
    • توفير خاصية Instance ثابتة تعيد المثيل الوحيد (استخدم نهج التهيئة الثابتة الأبسط لضمان أمان الخيوط)
    • تضمين حقل نصي خاص _appName تمت تهيئته بـ "MyApp"
    • امتلاك طريقة SetAppName(string name) لتحديث اسم التطبيق
    • امتلاك طريقة GetAppName() التي تعيد اسم التطبيق الحالي
  • Program.cs: في ملفك الرئيسي، أثبت أن Singleton يعمل بشكل صحيح من خلال الوصول إلى المثيل عدة مرات وإظهار أن التغييرات تستمر عبر جميع المراجع.

ستتلقى مدخلاً واحداً:

  • اسم تطبيق جديد لتعيينه (على سبيل المثال، ProductionServer)

في برنامجك:

  1. احصل على مثيل ConfigManager واطبع اسم التطبيق الافتراضي
  2. احصل على المثيل مرة أخرى (في متغير مختلف) واستخدمه لتعيين اسم التطبيق الجديد من المدخلات
  3. احصل على المثيل للمرة الثالثة واطبع اسم التطبيق لإثبات أن جميع المراجع تشترك في نفس البيانات

على سبيل المثال، إذا كان الإدخال هو ProductionServer، فيجب أن يكون الناتج:

MyApp
ProductionServer

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

ورقة مرجعية

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

نمط Singleton أساسي مع قفل صريح (explicit locking) لضمان سلامة الخيوط (thread safety):

public class Logger
{
    private static Logger _instance;
    private static readonly object _lock = new object();
    
    private Logger() { }  // Private constructor
    
    public static Logger Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                    _instance = new Logger();
                return _instance;
            }
        }
    }
    
    public void Log(string message) => Console.WriteLine(message);
}

تضمن عبارة lock سلامة الخيوط (thread safety) من خلال السماح لخيط واحد فقط بالدخول إلى القسم الحرج (critical section) في المرة الواحدة.

نمط Singleton أبسط وآمن للخيوط (thread-safe) باستخدام التهيئة الثابتة (static initialization):

public class Logger
{
    private static readonly Logger _instance = new Logger();
    
    private Logger() { }
    
    public static Logger Instance => _instance;
    
    public void Log(string message) => Console.WriteLine(message);
}

هذا النهج آمن للخيوط (thread-safe) لأن CLR يضمن حدوث تهيئة الحقول الثابتة (static field initialization) مرة واحدة فقط.

الاستخدام - يعيد دائماً نفس المثيل:

Logger.Instance.Log("First call");
Logger.Instance.Log("Same instance");

جرّب بنفسك

using System;
using Configuration;

class Program
{
    public static void Main(string[] args)
    {
        // قراءة المدخلات - اسم التطبيق الجديد
        string newAppName = Console.ReadLine();
        
        // TODO: احصل على مثيل ConfigManager واطبع اسم التطبيق الافتراضي
        
        // TODO: احصل على المثيل مرة أخرى (في متغير مختلف) واستخدمه لتعيين اسم التطبيق الجديد
        
        // TODO: احصل على المثيل للمرة الثالثة واطبع اسم التطبيق لإثبات أن جميع المراجع تشترك في نفس البيانات
    }
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming