Programming Basics SQL HTML CSS JavaScript Python C++ Java JavaFX Swing Problem Solving English English Conversations Computer Fundamentals Learn Typing

تعرف على نمط التصميم Singleton

  • مفهوم النمط Singleton
  • لماذا نحتاج النمط Singleton
  • آلية بناء كلاس بأسلوب Singleton
  • مزايا و عيوب النمط Singleton
  • أمثلة تطبيقية على النمط Singleton

مفهوم النمط Singleton

Singleton هو أحد أنماط التصميم البرمجي التي تندرج تحت الفئة الإنشائية ( Creating Patterns ) و المستخدمة على نطاق واسع في علم تطوير البرمجيات.

يهدف هذا النمط بناء الكلاس ( Class ) بشكل يكون بالإمكان إنشاء كائن ( Object ) واحد منه فقط و يكون بالإمكان الوصول إليه من أي مكان نريده في المشروع.

Singleton Pattern


يجب أن يكون لديك معرفة مسبقة بمفاهيم البرمجة الكائنية ( OOP ) حتى تتمكن من فهم آلية عمل هذا النمط.

لماذا نحتاج النمط Singleton

يحل نمط Singleton مشكلتين في نفس الوقت مخالفاً بذلك مبدأ المسؤولية الواحدة ( Single Responsibility principle ).


التحكم بعدد الكائنات

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

إذاً بعد يتم إنشاء الكائن لأول مرة فإنه عند الحاجة لإنشاء واحد جديد فإنه يتم إعادة استخدام الكائن نفسه طالما أنه موجود بالفعل بدلاً من إنشاء كائن جديد.

هذا السلوك في التعامل مع الكائنات لا يمكن تحقيقه في حال إنشاء كائنات بالطريقة التقليدية حيث أن الطريقة التقليدية تقضي دائماُ بإنشاء كائن جديد في الذاكرة كلما قمنا بإنشاء كائن منه.


الوصول العام للكائنات

النمط Singleton يوفر نقطة وصول عامة ( Global Access ) إلى النسخة الوحيدة أو الكائن الوحيد و الذي قد يحتوي على متغيرات و دوال عامة.

المتغيرات العامة في هذا الكائن مفيدة لتخزين المعلومات المراد الوصول إليها من أكثر من مكان في المشروع و لكن مع ذلك يجب مراعاة أن هذه المتغيرات الغير آمنة، حيث يمكن لأي كود آخر تعديلها، مما قد يتسبب في انهيار المشروع.

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


بعض استخدامات النمط Singleton

فيما يلي بعض الحالات التي يكون من المفيد استخدام النمط Singleton في برمجتها:

  • تسجيل الاخطاء التي قد تحدث.
  • إدارة جميع الإتصالات التي يتم إجراءها مع قاعدة البيانات.
  • إدارة الملفات التي سيتم طباعتها.
  • إدارة الملفات التي تحتوي معلومات مرتبطة بالمشروع.
  • حفظ معلومات الضبط الخاصة بالمشروع.
  • حفظ معلومات في ذاكرة مؤقتة ( Cache memory ) لتقليل الطلبات إلى خادم قاعدة البيانات.

آلية بناء كلاس بأسلوب Singleton

أسلوب النمط Singleton يتم تحقيقه من خلال اتباع القواعد التالية:

  1. إخفاء الكونستركتور الإفتراضي في الكلاس، أي جعله private لمنع إنشاء كائنات من الكلاس بالطريقة التقليدية باستخدام الكلمة المفتاحية new.
  2. تعريف خاصيّة ( Property ) في الكلاس لأنه سيتم تخزين الكائن الذي يتم إنشاؤه منه فيها.
  3. تعريف دالة عامة في الكلاس، أي يكون نوعها public static و من خلالها يمكن الحصول على الكائن الذي تم إنشاؤه مسبقاً من الكلاس أو الحصول على كائن جديد منه و كأنها كونستركتور.

عند استدعاء الدالة العامة الموجودة في الكلاس فإن أول ما تقوم به هو فحص قيمة الخاصيّة العامة الموجودة فيه.

  • إذا وجدت أن الخاصية فيها كائن فإنها ترجعه نفسه فقط.
  • إذا وجدت أن الخاصية لا يوجد فيها كائن ( قيمتها null ) فإنها تقوم بإنشاء كائن جديد من الكلاس و تخزنه فيها و من ثم ترجعه لمن قام باستدعاء الدالة.

أي استدعاء جديد للدالة ستكون الخاصية بالفعل فيها كائن من الكلاس و بالتالي سترجعه نفسه.


بنية النمط Singleton

مخطط الكلاس ( Class Diagram ) في حال استخدام النمط Singleton يجب أن يكون على النحو التالي:

Singleton class diagram

لو كان المشروع يدعم تعدد المهام ( Multithreading ) كنا سنضيف حينها قفل للثريد ( Thread lock ) عند إنشاء الكائن.

مزايا و عيوب النمط Singleton

فيما يلي سنذكر مزايا و عيوب هذا النمط حتى تعرف متى يكون استخدامه مناسباً و متى لا يكون كذلك.


مزايا النمط

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

عيوب النمط

  1. مشاكل في الأداء إذا لم يتم تنفيذه بشكل صحيح في المشروع الذي يستخدمه في أكثر من Thread حيث قد يتم إنشاء نسخ متعددة من الكائن.
  2. صعوبة التوسع في حالة الحاجة إلى تغيير نوع النمط لتوفير عدة نسخ من الكلاس، قد يكون من الصعب تحقيق هذا التغيير بشكل فعال دون تأثيرات جانبية على الكود الحالي المبني على النمط السابق.

أمثلة تطبيقية على النمط Singleton

في المثال التالي قمنا ببناء كلاس يطبق النمط Singleton بأبسط شكل ممكن بدون استخدام قفل.

المثال الأول

using System;
// وضعناها لمنع الوراثة من الكلاس و بالتالي منع إنشاء كائن منه sealed الكلمة
public sealed class Singleton
{
// قمنا بتجهيزها لاحتواء الكائن الوحيد الذي يمكن إنشاؤه من هذا الكلاس _instance الخاصية المخفية
private static Singleton _instance = null;
// لمنع الوصول إليه من خارج الكلاس (private) هنا قمنا بجعل الكونستركتور الإفتراضي مخفي
private Singleton() { }
// هنا قمنا بإنشاء دالة عامة في الكلاس و التي يمكن من خلالها الوصول إلى الكائن
public static Singleton Instance
{
get
{
// _instance عند طلب هذه الخاصية فإنها تفحص قيمة الخاصية المخفية
// إذا لم تجد فيها كائن فإنها ستقوم بإنشاء واحد و من ثم تخزنه فيها
if (_instance == null)
{
_instance = new Singleton();
}
// _instance هنا قمنا بإرجاع الكائن المخزن في الخاصية المخفية
return _instance;
}
}
// يمكنك إضافة دوال و خصائص أخرى في الكلاس كما يلي
public string GetData()
{
return "This data is from the Singleton instance.";
}
}
public class Program
{
static void Main(string[] args)
{
// الموجودة فيه Instance عن طريق استدعاء الخاصية Singleton هنا حاولنا إنشاء كائنين من الكلاس
Singleton instance1 = Singleton.Instance;
Singleton instance2 = Singleton.Instance;
// لنعرف ما إن كانا فعلاً يشيران لنفس الكائن في الذاكرة أم لا instance1 و instance2 هنا قمنا بمقارنة قيمة
if (instance1 == instance2)
{
// بمجرد أن تتنفذ هذه الأوامر فهذا يعني أن كلا الكائنان يشيران إلى نفس الكائن في الذاكرة
Console.WriteLine("It's the same Singleton instance!");
Console.WriteLine(instance1.GetData());
}
}
}
using System; // وضعناها لمنع الوراثة من الكلاس و بالتالي منع إنشاء كائن منه sealed الكلمة public sealed class Singleton { // قمنا بتجهيزها لاحتواء الكائن الوحيد الذي يمكن إنشاؤه من هذا الكلاس _instance الخاصية المخفية private static Singleton _instance = null; // لمنع الوصول إليه من خارج الكلاس (private) هنا قمنا بجعل الكونستركتور الإفتراضي مخفي private Singleton() { } // هنا قمنا بإنشاء دالة عامة في الكلاس و التي يمكن من خلالها الوصول إلى الكائن public static Singleton Instance { get { // _instance عند طلب هذه الخاصية فإنها تفحص قيمة الخاصية المخفية // إذا لم تجد فيها كائن فإنها ستقوم بإنشاء واحد و من ثم تخزنه فيها if (_instance == null) { _instance = new Singleton(); } // _instance هنا قمنا بإرجاع الكائن المخزن في الخاصية المخفية return _instance; } } // يمكنك إضافة دوال و خصائص أخرى في الكلاس كما يلي public string GetData() { return "This data is from the Singleton instance."; } } public class Program { static void Main(string[] args) { // الموجودة فيه Instance عن طريق استدعاء الخاصية Singleton هنا حاولنا إنشاء كائنين من الكلاس Singleton instance1 = Singleton.Instance; Singleton instance2 = Singleton.Instance; // لنعرف ما إن كانا فعلاً يشيران لنفس الكائن في الذاكرة أم لا instance1 و instance2 هنا قمنا بمقارنة قيمة if (instance1 == instance2) { // بمجرد أن تتنفذ هذه الأوامر فهذا يعني أن كلا الكائنان يشيران إلى نفس الكائن في الذاكرة Console.WriteLine("It's the same Singleton instance!"); Console.WriteLine(instance1.GetData()); } } }

نتيجة التشغيل:

It's the same Singleton instance!
This data is from the Singleton instance.

في المثال التالي قمنا ببناء كلاس يطبق النمط Singleton مع استخدام قفل ( Lock ) أثناء إنشاء كائن منه.
ملاحظة: هذا الأسلوب يجب استخدامه في حال كان النمط سيُستعمل ضمن Thread.

المثال الثاني

using System;
// وضعناها لمنع الوراثة من الكلاس و بالتالي منع إنشاء كائن منه sealed الكلمة
public sealed class Singleton
{
// قمنا بتجهيزها لاحتواء الكائن الوحيد الذي يمكن إنشاؤه من هذا الكلاس _instance الخاصية المخفية
private static Singleton _instance = null;
// الكائن التالي سنستخدمه كقفل عند إنشاء الكائن الوحيد من الكلاس
private static readonly object _syncLock = new object();
// لمنع الوصول إليه من خارج الكلاس (private) هنا قمنا بجعل الكونستركتور الإفتراضي مخفي
private Singleton() { }
// هنا قمنا بإنشاء دالة عامة في الكلاس و التي يمكن من خلالها الوصول إلى الكائن
public static Singleton Instance
{
get
{
// _instance عند طلب هذه الخاصية فإنها تفحص قيمة الخاصية المخفية
// إذا لم تجد فيها كائن فإنها ستقوم باستخدام القفل، ثم إنشاء واحد، ثم تخزنه فيها
if (_instance == null)
{
lock (_syncLock)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
}
// _instance هنا قمنا بإرجاع الكائن المخزن في الخاصية المخفية
return _instance;
}
}
// يمكنك إضافة دوال و خصائص أخرى في الكلاس كما يلي
public string GetData()
{
return "This data is from the Singleton instance.";
}
}
public class Program
{
static void Main(string[] args)
{
// الموجودة فيه Instance عن طريق استدعاء الخاصية Singleton هنا حاولنا إنشاء كائنين من الكلاس
Singleton instance1 = Singleton.Instance;
Singleton instance2 = Singleton.Instance;
// لنعرف ما إن كانا فعلاً يشيران لنفس الكائن في الذاكرة أم لا instance1 و instance2 هنا قمنا بمقارنة قيمة
if (instance1 == instance2)
{
// بمجرد أن تتنفذ هذه الأوامر فهذا يعني أن كلا الكائنان يشيران إلى نفس الكائن في الذاكرة
Console.WriteLine("It's the same Singleton instance!");
Console.WriteLine(instance1.GetData());
}
}
}
using System; // وضعناها لمنع الوراثة من الكلاس و بالتالي منع إنشاء كائن منه sealed الكلمة public sealed class Singleton { // قمنا بتجهيزها لاحتواء الكائن الوحيد الذي يمكن إنشاؤه من هذا الكلاس _instance الخاصية المخفية private static Singleton _instance = null; // الكائن التالي سنستخدمه كقفل عند إنشاء الكائن الوحيد من الكلاس private static readonly object _syncLock = new object(); // لمنع الوصول إليه من خارج الكلاس (private) هنا قمنا بجعل الكونستركتور الإفتراضي مخفي private Singleton() { } // هنا قمنا بإنشاء دالة عامة في الكلاس و التي يمكن من خلالها الوصول إلى الكائن public static Singleton Instance { get { // _instance عند طلب هذه الخاصية فإنها تفحص قيمة الخاصية المخفية // إذا لم تجد فيها كائن فإنها ستقوم باستخدام القفل، ثم إنشاء واحد، ثم تخزنه فيها if (_instance == null) { lock (_syncLock) { if (_instance == null) { _instance = new Singleton(); } } } // _instance هنا قمنا بإرجاع الكائن المخزن في الخاصية المخفية return _instance; } } // يمكنك إضافة دوال و خصائص أخرى في الكلاس كما يلي public string GetData() { return "This data is from the Singleton instance."; } } public class Program { static void Main(string[] args) { // الموجودة فيه Instance عن طريق استدعاء الخاصية Singleton هنا حاولنا إنشاء كائنين من الكلاس Singleton instance1 = Singleton.Instance; Singleton instance2 = Singleton.Instance; // لنعرف ما إن كانا فعلاً يشيران لنفس الكائن في الذاكرة أم لا instance1 و instance2 هنا قمنا بمقارنة قيمة if (instance1 == instance2) { // بمجرد أن تتنفذ هذه الأوامر فهذا يعني أن كلا الكائنان يشيران إلى نفس الكائن في الذاكرة Console.WriteLine("It's the same Singleton instance!"); Console.WriteLine(instance1.GetData()); } } }

نتيجة التشغيل:

It's the same Singleton instance!
This data is from the Singleton instance.

في المثال السابق تم تم استخدم قفل التزامن لضمان أنه في حالة تواجد أكثر من Thread يحاول الوصول إلى الكائن في نفس الوقت، فإنهم سينتظرون بالتسلسل حتى يتمكنوا من الوصول إلى الكائن. هذا الأسلوب يمنع بشكل فعال إنشاء عدة كائنات في نفس الوقت.

لا يزال هناك مشكلة يحتمل وقوعها و هي أن استخدام lock لقفل التزامن قد يؤدي إلى أداء ضعيف في حالة حدوث الكثير من الطلبات للوصول إلى الكائن في نفس الوقت، حيث أن كل Thread سيكون في حالة انتظار حتى يتمكن من الوصول إلى الكائن و هذا قد يؤدي إلى ما يُعرف بحالة الاختناق ( Deadlock ) إذا لم تتم إدارة القفل بشكل صحيح.


معالجة حالة الإختناق

لمعالجة حالة الإختناق نستخدم إحدى الطريقتين:

  • التحميل الكسول ( Lazy Loading )
  • التحميل المبكر ( Eager Loading )

ما هو التحميل الكسول

  1. يعني تأجيل تحميل البيانات أو الكائنات حتى يتم طلبها لأول مرة.
  2. يستخدم غالبًا لتحسين أداء التطبيق وتقليل استهلاك الموارد، خاصة عندما تكون البيانات أو الكائنات غير مطلوبة في كل الحالات.
  3. يتم تحميل البيانات أو الكائنات فقط عندما يكون هناك طلب مباشر للوصول إليها.

ما هو التحميل المبكر

  1. يعني تحميل البيانات أو الكائنات مباشرةً عند بدء تشغيل التطبيق.
  2. يضمن أن البيانات أو الكائنات متاحة دائمًا وجاهزة للاستخدام بمجرد الحاجة إليها.
  3. قد يسبب تحميل غير ضروري للبيانات أو الكائنات التي قد لا يتم استخدامها في كل الحالات، مما يؤدي إلى استهلاك زائد للموارد في بعض الأحيان.

يستخدم Lazy Loading لتحميل البيانات أو الكائنات بشكل ديناميكي عند الطلب، بينما يستخدم Eager Loading لتحميلها مسبقاً لتكون جاهزة للاستخدام بمجرد بدء تشغيل التطبيق أو تحميل الكلاس.


في المثال التالي قمنا ببناء كلاس يطبق النمط Singleton بأسلوب Lazy Loading.

المثال الثالث

using System;
// وضعناها لمنع الوراثة من الكلاس و بالتالي منع إنشاء كائن منه sealed الكلمة
public sealed class Singleton
{
// Lazy Loading قمنا بتجهيزها لاحتواء الكائن الوحيد الذي يمكن إنشاؤه من هذا الكلاس و بأسلوب _instance الخاصية المخفية
private static readonly Lazy<Singleton> _instance = new Lazy<Singleton>(() => new Singleton());
// لمنع الوصول إليه من خارج الكلاس (private) هنا قمنا بجعل الكونستركتور الإفتراضي مخفي
private Singleton() { }
// هنا قمنا بإنشاء دالة عامة في الكلاس و التي يمكن من خلالها الوصول إلى الكائن
public static Singleton Instance => _instance.Value;
// يمكنك إضافة دوال و خصائص أخرى في الكلاس كما يلي
public string GetData()
{
return "This data is from the Singleton instance.";
}
}
public class Program
{
static void Main(string[] args)
{
// الموجودة فيه Instance عن طريق استدعاء الخاصية Singleton هنا حاولنا إنشاء كائنين من الكلاس
Singleton instance1 = Singleton.Instance;
Singleton instance2 = Singleton.Instance;
// لنعرف ما إن كانا فعلاً يشيران لنفس الكائن في الذاكرة أم لا instance1 و instance2 هنا قمنا بمقارنة قيمة
if (instance1 == instance2)
{
// بمجرد أن تتنفذ هذه الأوامر فهذا يعني أن كلا الكائنان يشيران إلى نفس الكائن في الذاكرة
Console.WriteLine("It's the same Singleton instance!");
Console.WriteLine(instance1.GetData());
}
}
}
using System; // وضعناها لمنع الوراثة من الكلاس و بالتالي منع إنشاء كائن منه sealed الكلمة public sealed class Singleton { // Lazy Loading قمنا بتجهيزها لاحتواء الكائن الوحيد الذي يمكن إنشاؤه من هذا الكلاس و بأسلوب _instance الخاصية المخفية private static readonly Lazy<Singleton> _instance = new Lazy<Singleton>(() => new Singleton()); // لمنع الوصول إليه من خارج الكلاس (private) هنا قمنا بجعل الكونستركتور الإفتراضي مخفي private Singleton() { } // هنا قمنا بإنشاء دالة عامة في الكلاس و التي يمكن من خلالها الوصول إلى الكائن public static Singleton Instance => _instance.Value; // يمكنك إضافة دوال و خصائص أخرى في الكلاس كما يلي public string GetData() { return "This data is from the Singleton instance."; } } public class Program { static void Main(string[] args) { // الموجودة فيه Instance عن طريق استدعاء الخاصية Singleton هنا حاولنا إنشاء كائنين من الكلاس Singleton instance1 = Singleton.Instance; Singleton instance2 = Singleton.Instance; // لنعرف ما إن كانا فعلاً يشيران لنفس الكائن في الذاكرة أم لا instance1 و instance2 هنا قمنا بمقارنة قيمة if (instance1 == instance2) { // بمجرد أن تتنفذ هذه الأوامر فهذا يعني أن كلا الكائنان يشيران إلى نفس الكائن في الذاكرة Console.WriteLine("It's the same Singleton instance!"); Console.WriteLine(instance1.GetData()); } } }

نتيجة التشغيل:

It's the same Singleton instance!
This data is from the Singleton instance.

في المثال التالي قمنا ببناء كلاس يطبق النمط Singleton بأسلوب Eager Loading.

المثال الرابع

using System;
// وضعناها لمنع الوراثة من الكلاس و بالتالي منع إنشاء كائن منه sealed الكلمة
public sealed class Singleton
{
// Eager Loading قمنا بتجهيزها لاحتواء الكائن الوحيد الذي يمكن إنشاؤه من هذا الكلاس و بأسلوب _instance الخاصية المخفية
private static readonly Singleton _instance = new Singleton();
// لمنع الوصول إليه من خارج الكلاس (private) هنا قمنا بجعل الكونستركتور الإفتراضي مخفي
private Singleton() { }
// هنا قمنا بإنشاء دالة عامة في الكلاس و التي يمكن من خلالها الوصول إلى الكائن
public static Singleton Instance => _instance;
// يمكنك إضافة دوال و خصائص أخرى في الكلاس كما يلي
public string GetData()
{
return "This data is from the Singleton instance.";
}
}
public class Program
{
static void Main(string[] args)
{
// الموجودة فيه Instance عن طريق استدعاء الخاصية Singleton هنا حاولنا إنشاء كائنين من الكلاس
Singleton instance1 = Singleton.Instance;
Singleton instance2 = Singleton.Instance;
// لنعرف ما إن كانا فعلاً يشيران لنفس الكائن في الذاكرة أم لا instance1 و instance2 هنا قمنا بمقارنة قيمة
if (instance1 == instance2)
{
// بمجرد أن تتنفذ هذه الأوامر فهذا يعني أن كلا الكائنان يشيران إلى نفس الكائن في الذاكرة
Console.WriteLine("It's the same Singleton instance!");
Console.WriteLine(instance1.GetData());
}
}
}
using System; // وضعناها لمنع الوراثة من الكلاس و بالتالي منع إنشاء كائن منه sealed الكلمة public sealed class Singleton { // Eager Loading قمنا بتجهيزها لاحتواء الكائن الوحيد الذي يمكن إنشاؤه من هذا الكلاس و بأسلوب _instance الخاصية المخفية private static readonly Singleton _instance = new Singleton(); // لمنع الوصول إليه من خارج الكلاس (private) هنا قمنا بجعل الكونستركتور الإفتراضي مخفي private Singleton() { } // هنا قمنا بإنشاء دالة عامة في الكلاس و التي يمكن من خلالها الوصول إلى الكائن public static Singleton Instance => _instance; // يمكنك إضافة دوال و خصائص أخرى في الكلاس كما يلي public string GetData() { return "This data is from the Singleton instance."; } } public class Program { static void Main(string[] args) { // الموجودة فيه Instance عن طريق استدعاء الخاصية Singleton هنا حاولنا إنشاء كائنين من الكلاس Singleton instance1 = Singleton.Instance; Singleton instance2 = Singleton.Instance; // لنعرف ما إن كانا فعلاً يشيران لنفس الكائن في الذاكرة أم لا instance1 و instance2 هنا قمنا بمقارنة قيمة if (instance1 == instance2) { // بمجرد أن تتنفذ هذه الأوامر فهذا يعني أن كلا الكائنان يشيران إلى نفس الكائن في الذاكرة Console.WriteLine("It's the same Singleton instance!"); Console.WriteLine(instance1.GetData()); } } }

نتيجة التشغيل:

It's the same Singleton instance!
This data is from the Singleton instance.
آخر تحديث في 20-04-2024

الكاتب

ibrahim alomar

طالب جامعي في السنة الاخير في كلية الهندسة المعلوماتية، . لدي شغف كبير بمجالات تطوير البرمجيات وبرمجة تطبيقات الويب ولدي إهتمام كبير في حل المشكلات والخوارزميات , قمت بالعمل على العديد من المشاريع البرمجية

تعليقات

لا يوجد أي تعليق بعد

أضف تعليق

يجب تسجيل الدخول حتى تتمكن من إضافة تعليق أو رد.