نمط Singleton
جزء من قسم Object Oriented Programming في رحلة C على Coddy — الدرس 54 من 61.
أحياناً تحتاج إلى نسخة واحدة بالضبط من كائن ما طوال برنامجك بالكامل — مدير إعدادات، أو نظام تسجيل، أو اتصال بقاعدة بيانات. يضمن نمط Singleton وجود نسخة واحدة فقط ويوفر نقطة وصول عامة إليها.
في لغة C، نقوم بتنفيذ ذلك باستخدام متغير static داخل ملف المصدر. تذكر أن static على نطاق الملف يعني أن المتغير خاص بهذا الملف ويستمر طوال فترة حياة البرنامج. وبدمجه مع دالة جلب (getter function)، فإن هذا ينشئ الـ Singleton الخاص بنا:
// config.c
#include "config.h"
#include <stdlib.h>
struct Config {
int max_connections;
int timeout;
};
static Config* instance = NULL;
Config* get_config(void) {
if (instance == NULL) {
instance = malloc(sizeof(Config));
instance->max_connections = 100;
instance->timeout = 30;
}
return instance;
}يحدث السحر في get_config(). يقوم الاستدعاء الأول بإنشاء وتهيئة المثيل، وكل استدعاء لاحق يعيد نفس المؤشر.
تضمن الكلمة المفتاحية static أن instance يحتفظ بقيمته بين استدعاءات الدوال ويظل مخفياً عن الملفات الأخرى.
من الترويسة، يرى المستخدمون فقط دالة الجلب (getter function) — ولا يمكنهم الوصول إلى المتغير الساكن (static variable) أو تعديله مباشرة. وبغض النظر عن عدد المرات أو المكان الذي تستدعي منه get_config()، ستحصل دائماً على الكائن نفسه.
التحدي
سهللنقم ببناء وحدة Settings تدير تكوينات التطبيق بالكامل باستخدام نمط Singleton. بغض النظر عن عدد المرات التي يطلب فيها الكود الخاص بك الإعدادات، يجب أن يتلقى دائماً نفس النسخة (instance) تماماً.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
settings.h: قم بالتصريح عن نوعSettingsمبهم باستخدام تصريح مسبق (يبقى جسم الـ struct مخفياً). صرّح عن دالةget_settingsالتي تعيد مؤشراً إلى نسخة الـ singleton، بالإضافة إلى دوال getter و setter لقيمتي تكوين:volume(عدد صحيح) وbrightness(عدد صحيح). حواجز التضمين (Include guards) ضرورية هنا.settings.c: هنا يحدث السحر. قم بتعريف الـSettingsstruct الفعلي مع حقولvolumeوbrightness. أنشئ متغير مؤشرstaticللاحتفاظ بالنسخة الوحيدة (يتم تهيئته بـNULL). قم بتنفيذget_settingsبحيث تقوم الاستدعاءة الأولى بتخصيص الذاكرة وتهيئة النسخة بالقيم الافتراضية (volume: 50، brightness: 75)، بينما تعيد الاستدعاءات اللاحقة ببساطة النسخة الموجودة. قم بتنفيذ الـ getters والـ setters للوصول إلى الحقول المخفية وتعديلها.main.c: أثبت أن الـ Singleton يعمل بشكل صحيح. اقرأ عددين صحيحين يمثلان قيم مستوى الصوت والسطوع الجديدة. استدعِget_settingsمرتين، وقم بتخزين النتائج في متغيري مؤشر مختلفين. استخدم المؤشر الأول لتحديث مستوى الصوت والسطوع بالقيم المدخلة. ثم استخدم المؤشر الثاني لاسترداد وطباعة كلتا القيمتين. إذا كان الـ Singleton يعمل، فإن التغييرات التي تم إجراؤها من خلال مؤشر واحد ستكون مرئية من خلال الآخر.
سيتلقى برنامجك مدخلين:
- قيمة مستوى الصوت الجديدة
- قيمة السطوع الجديدة
اطبع مستوى الصوت والسطوع في سطرين منفصلين بعد تعديلهما من خلال مؤشر واحد وقراءتهما من خلال مؤشر آخر.
مثال على المخرجات عندما تكون المدخلات 80 و 100:
80
100مثال على المخرجات عندما تكون المدخلات 25 و 60:
25
60مثال على المخرجات عندما تكون المدخلات 0 و 50:
0
50الفكرة الأساسية هي أن كلا المؤشرين يشيران إلى نفس الكائن في الذاكرة — وهذا هو جوهر نمط Singleton. يضمن المتغير الساكن (static) داخل ملف المصدر الخاص بك وجود نسخة واحدة فقط على الإطلاق، وتستمر طوال فترة حياة البرنامج.
ورقة مرجعية
يضمن نمط Singleton وجود مثيل واحد فقط للكائن طوال البرنامج بأكمله ويوفر نقطة وصول عالمية إليه.
في لغة C، قم بتنفيذ Singleton باستخدام متغير static داخل ملف مصدر مدمج مع دالة جلب (getter function):
// config.c
#include "config.h"
#include <stdlib.h>
struct Config {
int max_connections;
int timeout;
};
static Config* instance = NULL;
Config* get_config(void) {
if (instance == NULL) {
instance = malloc(sizeof(Config));
instance->max_connections = 100;
instance->timeout = 30;
}
return instance;
}النقاط الرئيسية:
- تجعل الكلمة المفتاحية
staticعلى نطاق الملف المتغير خاصاً بذلك الملف ومستمراً طوال عمر البرنامج - تتحقق دالة الجلب (getter function) مما إذا كان المثيل
NULLعند الاستدعاء الأول، وتقوم بإنشائه وتهيئته، ثم تعيده - الاستدعاءات اللاحقة تعيد نفس المؤشر
- يرى المستخدمون دالة الجلب فقط من خلال ملف الترويسة (header) — ولا يمكنهم الوصول إلى المتغير الساكن أو تعديله مباشرة
- بغض النظر عن عدد المرات أو المكان الذي تستدعي منه دالة الجلب، ستحصل دائماً على نفس الكائن
جرّب بنفسك
#include <stdio.h>
#include "settings.h"
int main() {
int new_volume, new_brightness;
scanf("%d", &new_volume);
scanf("%d", &new_brightness);
// TODO: استدعِ get_settings مرتين وقم بتخزينهما في متغيري pointer مختلفين
// TODO: استخدم الـ pointer الأول لتحديث volume و brightness بالقيم المدخلة
// TODO: استخدم الـ pointer الثاني لاسترجاع وطباعة volume و brightness
// (هذا يوضح أن كلا الـ pointers يشيران إلى نفس الـ singleton instance)
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات البرمجة التركيبية
ملفات الترويسةحواجز التضمينملفات المصدرالدوال الساكنةمراجعة: آلة حاسبة تركيبية4التغليف (Encapsulation)
مفهوم الـ Opaque Pointersتعريف الـ Opaque Structsالـ Getters والـ Settersالتحقق من البيانات في الـ Settersملخص: الصندوق السري2الكائنات والأساليب
الـ Structs ككائناتمؤشر 'Self'صحة استخدام Constالمؤشر مقابل القيمةالأساليب المساعدةملخص: Point Manager5مشروع: حساب بنكي بسيط
إعداد المشروعتنفيذ الحساب3دورة حياة الكائن
نمط المنشئ (Constructor Pattern)نمط الهادم (Destructor Pattern)تهيئة الـ Stackالنسخ العميق (Deep Copy)مراجعة: String Wrapper6الوراثة عبر التركيب
تضمين الـ Structقاعدة العضو الأولالوصول إلى أعضاء الأبعملية الـ Upcastingمراجعة: هرمية الأشكال9مشروع: رسّام الأشكال
نظرة عامة على المشروعتنفيذ الدائرة