January 28, 2026 (3mo ago)

دليل نمط المفرد (Singleton) في جافا لكتابة كود نظيف

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

← Back to blog
Cover Image for دليل نمط المفرد (Singleton) في جافا لكتابة كود نظيف

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

دليل نمط المفرد (Singleton) في جافا لكتابة كود نظيف

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

المقدمة

نمط المفرد (Singleton) في جافا يضمن أن يكون للفئة مثيل واحد فقط ويوفر نقطة وصول عالمية واحدة إليه. هذا مفيد للأشياء مثل مديري التكوين، ومجمعات الاتصالات، أو خدمات التسجيل المركزية حيث يؤدي وجود مثيلات متعددة إلى حالة غير متسقة أو هدر في الموارد. فهم كيفية تنفيذ مفرد آمن وقابل للاختبار — ومتى يجب تجنّبه — أمر أساسي لكتابة كود جافا نظيف وقابل للصيانة.

فهم نمط التصميم Singleton

برج مراقبة حركة جوية محاط بطائرات ملونة في نمط دائري، يوضّح إدارة حركة الطيران.

تشبيه مفيد هو برج مراقبة حركة الطيران في المطار. لا تبني برجًا منفصلاً لكل طائرة؛ فكل طائرة تتواصل مع نفس البرج. البرج هو المصدر الوحيد للحقيقة. هذا هو الدور الذي يلعبه المفرد (Singleton) في التطبيق.

انتشر هذا النمط في أدبيات أنماط التصميم الكلاسيكية وفي أنظمة جافا المؤسسية1. المسؤوليات الأساسية له بسيطة:

  • ضمان وجود مثيل واحد — عادةً عن طريق جعل المُنشئ خاصًا.
  • توفير نقطة وصول عالمية — عادةً طريقة ثابتة مثل getInstance().

الخصائص الرئيسية

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

الخلاصة الرئيسية: يفرض نمط المفرد كائنًا واحدًا ووصولًا عالميًا محكمًا حتى تتواصل أجزاء التطبيق المختلفة مع نفس المثيل بالضبط.

تنفيذ Singletons آمنة للخيوط في جافا

رسم توضيحي لخزنة مع قفل، تستقبل سهامًا ملونة متعددة، يرمز إلى تخزين آمن.

مفرد مُؤخر التهيئة البسيط ليس آمنًا للخيوط. انظر هذا المثال الأساسي:

public class BasicLazySingleton {
    private static BasicLazySingleton instance;

    private BasicLazySingleton() {}

    public static BasicLazySingleton getInstance() {
        if (instance == null) {
            instance = new BasicLazySingleton();
        }
        return instance;
    }
}

في بيئة متعددة الخيوط، يمكن لخيطين ملاحظة أن instance == null وكل منهما ينشئ مثيلًا جديدًا. إصلاح مباشر هو تزامن getInstance()، لكن ذلك يسبب قفلًا غير ضروري عند كل استدعاء.

طريقة متزامنة (تعمل، لكنها مكلفة)

public class SynchronizedSingleton {
    private static SynchronizedSingleton instance;

    private SynchronizedSingleton() {}

    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

هذا يحل مشكلة أمان الخيوط لكنه يضر بالأداء لأن التزامن يعمل عند كل وصول.

Bill Pugh (مبدأ التحميل عند الطلب للحامل)

نهج أنظف هو مذهب "التحمّل عند الطلب للحامل" (Initialization-on-demand Holder). يوفر تهيئة مُؤخرة وأمان الخيوط بدون عبء التزامن لأن تهيئة الفئات آمنة للخيوط في JVM2.

public class BillPughSingleton {
    private BillPughSingleton() {}

    private static class SingletonHolder {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

يعتمد هذا على JVM لتحميل SingletonHolder فقط عندما تُستدعى getInstance()، وضمانات تهيئة الفئة توفر أمان الخيوط.

Singletons عبر Enum (موصى به)

يوصي Joshua Bloch باستخدام enum ذو عنصر واحد للمفرد. هذا موجز ويحمي ضد هجمات الانعكاس والتسلسل3.

public enum EnumSingleton {
    INSTANCE;

    public void someMethod() {
        // business logic
    }
}

الفوائد:

  • كود مختصر
  • أمان الخيوط مقدم من JVM
  • أمان عند التسلسل
  • حماية قوية ضد إنشاء مثيلات عبر الانعكاس

في معظم الحالات، يكون enum Singleton الخيار الأكثر متانة وقابلية للصيانة.

التكاليف الخفية لنمط المفرد

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

على الرغم من أن المفردات تحل مشكلة معينة، إلا أنها تُدخل تكاليف خفية: ترابط قوي، حالة عالمية، وانخفاض القابلية للاختبار.

عندما يستدعي الكود Singleton.getInstance()، تكون تلك الاعتمادية مخفية. العقد العام للفئة لا يكشف أنها تعتمد على كائن عالمي. هذا يؤدي إلى:

  • كود جامد يصعب تغييره
  • اختبارات هشة أو تتطلب المثيل العالمي الحقيقي
  • صعوبة في تشغيل الاختبارات بالتوازي بسبب الحالة المشتركة

مشاكل الاختبار

يصعب على المفردات جعل الاختبار الوحدوي المعزول سهلاً. لا يمكنك بسهولة استبدال محاكٍ (mock) مكانها، لذا غالبًا ما تستخدم الاختبارات التنفيذ الحقيقي. قد يسبب ذلك اختبارات بطيئة، ترابطًا عرضيًا مع أنظمة خارجية، وسلاسل CI هشة.

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

الحالة العالمية والاعتماديات المخفية

المفردة هي في الأساس متغير عالمي. الحالة العالمية تُغَيِّب تدفق المعلومات وتخلق اعتمادات متشابكة يصعب فكها. هذا يعقد عملية تصحيح الأخطاء ويبطئ التطوير.

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

البدائل الحديثة للمفردات

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

حقن التبعيات

يقلب حقن التبعيات (DI) المسؤولية: يصرح العملاء باعتمادياتهم، ويقوم حاوية خارجية بتوفيرها. هذا يجعل الاعتمادية صريحة وسهلة الاستبدال في الاختبارات. أطر DI مثل Spring وGuice تدير دورات حياة الكائنات وربطها لك4.

فوائد DI:

  • فك الترابط — المكونات تعتمد على التجريدات، لا على الفئات الملموسة
  • قابلية الاختبار — يمكنك حقن محاكيات أو بدائل
  • المرونة — تبديل التنفيذات عبر التكوين

هذا يتماشى مع ممارسات عكس التحكم وينتج أنظمة أوضح وأسهل في الصيانة7.

المصانع

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

مثيلات مقيدة النطاق

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

المفردات في أنظمة موزعة

المفرد المحلي ضمن JVM لا يمنحك مثيلًا واحدًا عبر عدة نسخ من الخدمة. تعمل الخدمات المصغرة على تشغيل عدة JVMs، لذا تحتاج حلولًا موزعة للحالة المشتركة، مثل Redis أو خدمة تكوين مركزية مثل Consul أو Spring Cloud Config6.

كيفية إعادة هيكلة المفردات من كود قديم

رجل يفكك كتلة أحادية معقدة وثقيلة إلى وحدات أصغر ملونة لحاوية.

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

استراتيجية خطوة بخطوة:

  1. قدم واجهة تصف الطرق العامة للمفرد، واجعل المفرد يَنفِذها.
  2. اجعل الاعتماديات صريحة عن طريق إضافة معلمات مُنشئ في الفئات التي تستخدم المفرد.
  3. استخدم مصنعًا أو حاوية DI (مثل Spring أو Guice) لتوفير التنفيذ كمثيل مدار.
  4. استبدل استدعاءات Singleton.getInstance() بالاعتماديات المحقونة عبر المنشئ.
  5. أزل كود المفرد الخاص بمجرد أن يستقبل جميع المستدعين الاعتماد خارجيًا.

الفوائد: تحسين التجزئة، قابلية الاختبار، والوضوح. تحوّل إعادة هيكلة المفردات قاعدة كود جامدة إلى مكونات مرنة وقابلة للاختبار.

الأسئلة الشائعة

هل نمط المفرد هو نمط مضاد (anti-pattern)؟

غالبًا، نعم. يُدخل المفرد حالة عالمية وترابطًا قويًا، مما يقلل القابلية للاختبار ويزيد تكلفة الصيانة على المدى الطويل. استخدمه بحذر وفضّل DI أو مثيلات مقيدة النطاق حيثما أمكن.

كيف يمكن كسر المفرد في جافا؟

يمكن للانعكاس والتسلسل إنشاء مثيلات جديدة ما لم تحمِ نفسك صراحةً ضد ذلك. استخدام enum للمفرد يتجنّب هذه المزالق3.

هل المفرد مناسب للخدمات المصغرة؟

لا. المفرد يقتصر على JVM، لذا يحصل كل مثيل خدمة على مفرده الخاص. للحالة المشتركة عبر الخدمات، استخدم أنظمة موزعة مثل Redis أو خدمات تكوين مركزية6.

ثلاث ملخصات سؤال وجواب موجزة

س: متى يجب أن أستخدم المفرد؟ ج: فقط عندما يمثل المثيل الواحد موردًا عالميًا حقيقيًا داخل JVM واحد ولا يوجد بديل أفضل. فضّل enum Singletons إذا كان لابد من استخدامها.

س: كيف أجعل مفردًا مؤخر التهيئة وآمنًا للخيوط؟ ج: استخدم مذهب التحميل عند الطلب للحامل (Bill Pugh) أو enum. كلاهما يمنح أمان الخيوط مع أقل عبء؛ كما أن enum يحمي من مشاكل التسلسل والانعكاس23.

س: ماذا أستخدم بدل المفرد لتحسين قابلية الاختبار؟ ج: استخدم حقن التبعيات، المصانع، أو المثيلات مقيدة النطاق بحيث تكون الاعتماديات صريحة وسهلة الاستبدال في الاختبارات47.


1.
Design Patterns: Elements of Reusable Object-Oriented Software (the “Gang of Four”), Erich Gamma et al., 1994. https://en.wikipedia.org/wiki/Design_Patterns
2.
Java Language Specification, section on initialization of classes and interfaces; class initialization is performed at first active use and is thread-safe. https://docs.oracle.com/javase/specs/
3.
Joshua Bloch, Effective Java — recommends enum singletons for serialization and reflection safety. https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000006973/9780134685991
4.
Spring Framework documentation on Dependency Injection and bean scopes. https://spring.io/projects/spring-framework
5.
Serialization and reflection pitfalls are discussed in Effective Java and Java serialization documentation. https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
6.
Microservices patterns and guidance on distributed state; single-JVM singletons don’t provide cross-service singleton semantics. See Microservices.io and related resources. https://microservices.io/
7.
Martin Fowler on Inversion of Control and Dependency Injection principles. https://martinfowler.com/articles/injection.html
← Back to blog
🙋🏻‍♂️

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

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