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

نمط Singleton

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 86 من 107.

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

في لغة Go، نقوم بتنفيذ نمط Singleton باستخدام متغير على مستوى الحزمة مدمجاً مع sync.Once لضمان تهيئة آمنة برمجياً (thread-safe):

package config

import "sync"

type Config struct {
    DatabaseURL string
    MaxRetries  int
}

var (
    instance *Config
    once     sync.Once
)

func GetInstance() *Config {
    once.Do(func() {
        instance = &Config{
            DatabaseURL: "localhost:5432",
            MaxRetries:  3,
        }
    })
    return instance
}

تضمن sync.Once تشغيل دالة التهيئة مرة واحدة فقط، حتى عندما تستدعي عدة goroutines دالة GetInstance() في وقت واحد. يعيد كل استدعاء لاحق نفس المثيل دون إعادة تنفيذ كود التهيئة.

cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 و cfg2 يشيران إلى نفس المثيل

استخدم نمط Singleton باعتدال. فعلى الرغم من كونه ملائماً، إلا أنه يقدم حالة عامة (global state) يمكن أن تجعل الاختبار أكثر صعوبة وتخفي التبعيات. فكر فيما إذا كان حقن التبعية (dependency injection) قد يكون بديلاً أفضل لحالة الاستخدام الخاصة بك.

challenge icon

التحدي

سهل

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

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

  • logger.go: قم بتنفيذ مسجل Singleton آمن من حيث الخيوط (thread-safe).

    أنشئ هيكل Logger يحتوي على حقلين: Prefix (string) لبادئات رسائل السجل، و MessageCount (int) لتتبع عدد الرسائل التي تم تسجيلها.

    استخدم متغيرات على مستوى الحزمة (package-level) مع sync.Once لضمان إنشاء مثيل واحد فقط على الإطلاق. قم بتنفيذ دالة GetLogger() التي تقوم بتهيئة المسجل ببادئة افتراضية هي "[LOG]" وتعيد مثيل Singleton.

    أضف هذه الأساليب (methods) إلى Logger الخاص بك:

    • SetPrefix(prefix string) - يغير بادئة المسجل
    • Log(message string) string - يزيد عداد الرسائل ويعيد سلسلة نصية منسقة: [prefix] #[count]: [message]
    • GetCount() int - يعيد إجمالي عدد الرسائل المسجلة
  • main.go: أثبت أن الاستدعاءات المتعددة لـ GetLogger() تعيد نفس المثيل.

    اقرأ قيمة بادئة جديدة، ثم اقرأ عدد الرسائل متبوعاً بكل رسالة لتسجيلها.

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

سيتم توفير المدخلات التالية:

  • السطر 1: البادئة المخصصة لضبطها
  • السطر 2: عدد الرسائل
  • الأسطر التالية: كل رسالة لتسجيلها

على سبيل المثال، إذا كان المعطى:

[APP]
3
Server started
User connected
Request processed

يجب أن يكون مخرجك:

[APP] #1: Server started
[APP] #2: User connected
[APP] #3: Request processed
Total messages logged: 3

وإذا كان المعطى:

[DEBUG]
2
Initializing cache
Cache ready

يجب أن يكون مخرجك:

[DEBUG] #1: Initializing cache
[DEBUG] #2: Cache ready
Total messages logged: 2

وإذا كان المعطى:

[ERROR]
1
Connection failed

يجب أن يكون مخرجك:

[ERROR] #1: Connection failed
Total messages logged: 1

الفكرة الأساسية هي أنه بغض النظر عن عدد المرات التي تستدعي فيها GetLogger()، فإنك تحصل دائماً على نفس المثيل بحالته المشتركة. يستمر عداد الرسائل لأن هناك Logger واحداً فقط موجوداً!

ورقة مرجعية

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

قم بتنفيذ Singleton باستخدام متغير على مستوى الحزمة (package-level variable) مدمج مع sync.Once لضمان تهيئة آمنة برمجياً (thread-safe):

package config

import "sync"

type Config struct {
    DatabaseURL string
    MaxRetries  int
}

var (
    instance *Config
    once     sync.Once
)

func GetInstance() *Config {
    once.Do(func() {
        instance = &Config{
            DatabaseURL: "localhost:5432",
            MaxRetries:  3,
        }
    })
    return instance
}

تضمن sync.Once تشغيل دالة التهيئة مرة واحدة فقط، حتى عندما تستدعي عدة goroutines دالة GetInstance() في وقت واحد. كل استدعاء لاحق يعيد نفس المثيل:

cfg1 := config.GetInstance()
cfg2 := config.GetInstance()
// cfg1 and cfg2 point to the same instance

استخدم Singleton بحذر لأنه يقدم حالة عامة (global state) يمكن أن تجعل الاختبار أكثر صعوبة وتخفي التبعيات (dependencies). فكر في استخدام حقن التبعية (dependency injection) كبديل.

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// قراءة البادئة المخصصة
	prefix, _ := reader.ReadString('\n')
	prefix = strings.TrimSpace(prefix)

	// قراءة عدد الرسائل
	countStr, _ := reader.ReadString('\n')
	countStr = strings.TrimSpace(countStr)
	count, _ := strconv.Atoi(countStr)

	// قراءة كل رسالة
	messages := make([]string, count)
	for i := 0; i < count; i++ {
		msg, _ := reader.ReadString('\n')
		messages[i] = strings.TrimSpace(msg)
	}

	// TODO: GetLogger() الحصول على مثيل المسجل باستخدام 

	// TODO: SetPrefix() تعيين البادئة المخصصة باستخدام 

	// TODO: تسجيل كل رسالة وطباعة النتيجة

	// TODO: الحصول على مثيل المسجل مرة أخرى (لإثبات أنه نفس المثيل)
	// "Total messages logged: X" وطباعة إجمالي عدد الرسائل بالتنسيق التالي: 
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming