نمط المفردة Singleton

من موسوعة حسوب
اذهب إلى: تصفح، ابحث

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

محتويات

المشكلة

يحل نمط المفردة مشكلتين في نفس الوقت مخالفًا بذلك مبدأ المسؤولية الواحدة:

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

ثانيًا: توفير نقطة وصول عامة إلى تلك النسخة. ربما تكون المتغيرات العامة التي كنت تستخدمها لتخزين بعض الكائنات الضرورية مفيدة لكنها غير آمنة بما أن أي شيفرة قد تلغي محتويات تلك المتغيرات وتتسبب في انهيار التطبيق. ويسمح لك نمط المفردة هنا بالوصول إلى بعض الكائنات من أي مكان في البرنامج -تمامًا كالمتغير العام (Global Variable)- ، لكنه يحمي تلك النسخة من تغيير محتواها من قِبل الشيفرات الأخرى.

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

الحل

تشترك كل استخدامات نمط المفردة في هاتين الخطوتين:

  • جعل المنشئ الافتراضي خاصًا (private) لمنع الكائنات الأخرى من استخدام معامل new مع فئة المفردة.
  • إنشاء أسلوب إنشاء ساكن (static creation method) يتصرف مثل المنشئ (Constructor)، ويستدعي هذا الأسلوبُ المنشئَ الخاص من أجل إنشاء كائن ما ثم يحفظه بعد ذلك في حقل ساكن (static field)، وكل الاستدعاءات التالية لهذا الأسلوب تعيد الكائن المحفوظ مسبقًا (Cached).

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

مثال واقعي

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

البُنية

ش.1
  1. تصرح فئة Singleton عن الأسلوب الساكن getInstance الذي يعيد نفس النسخة من فئته. وينبغي أن يكون منشئ المفردة (Singleton's Constructor) مخفيًا من شيفرة العميل، وكذلك يجب أن يكون أسلوب getInstance هو الطريقة الوحيدة للحصول على كائن مفردة.

مثال توضيحي

تتصرف فئة وصلة قاعدة البيانات في هذا المثال كمفردة، وليس لتلك الفئة منشئ عام، لذا فإن الطريقة الوحيدة للحصول على كائنها هو استدعاء أسلوب getInstance ، إذ يحفظ أول كائن تم إنشاؤه ثم يعيده في كل الاستدعاءات التالية.
// الذي يسمح للعملاء بالوصول getInstance تعرِّف فئة قاعدة البيانات أسلوب
// إلى نفس النسخة من وصلة قاعدة البيانات في البرنامج كله.
class Database is
    private field instance: Database

    // يجب أن يكون منشئ المفردة خاصًا دائمًا لمنع استدعاءات الإنشاء المباشرة
    // new باستخدام معامل
    private constructor Database() is
        // شيفرة تهيئة، مثل الوصلة الفعلية لخادم قاعدة بيانات

    // الأسلوب الساكن الذي يتحكم في الوصول إلى نسخة المفردة.
    static method getInstance() is
        if (this.instance == null) then
            acquireThreadLock() and then
                // (thread) تأكد أن النسخة لم تُبدأ بعد بواسطة أي خيط
                // آخر إن كان هذا الخيط بانتظار تحرير القفل.
                if (this.instance == null) then
                    this.instance = new Database()
        return this.instance

    // أخيرًا، يجب أن تعرِّف أي مفردة بعض المنطق التجاري الذي يمكن
    // تنفيذه في هذه النسخة.
    public method query(sql) is
        // فمثلًا، ستمر كل استعلامات تطبيق ما على هذه الأسلوب، ومن ثم
        //  هنا(caching) أو حفظ مؤقت (throttling) يمكنك وضع منطق خنق.

class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ...")
        // ...
        Database bar = Database.getInstance()
        bar.query("SELECT ...")
        // foo على نفس الكائن الذي يحتويه متغير bar سيحتوي متغير.

قابلية الاستخدام

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

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

  • استخدم نمط المفردة حين تريد تحكمًا أكثر في متغيراتك العامة.

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

كيفية الاستخدام

  1. أضف حقلًا ساكنًا خاصًا إلى الفئة لتخزين نسخة المفردة.
  2. صرِّح عن أسلوب إنشاءٍ ساكنٍ عام للحصول على نسخة المفردة.
  3. استخدم التهيئة البطيئة (lazy initialization) داخل الأسلوب الساكن، إذ يجب أن تنشئ كائنًا جديدًا في الاستدعاء الأول لها وتضعه في الحقل الساكن. كذلك يجب أن يعيد الأسلوب تلك النسخة دومًا في كل الاستدعاءات التالية.
  4. اجعل منشئ الفئة خاصًا (private)، وسيظل أسلوب الفئة الساكن قادرًا على استدعاء المنشئ لكن لا يستطيع استدعاء الكائنات الأخرى.
  5. اذهب لشيفرة العميل واستبدل كل الاستدعاءات المباشرة لمنشئ المفردة باستدعاءات إلى أسلوب الإنشاء الساكن الخاص بها.

المزايا والعيوب

المزايا

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

العيوب

  • يخرق نمط المفردة مبدأ المسؤولية الواحدة، إذ يحل النمط مشكلتين في نفس الوقت.
  • يغطي على عيوب التصميم، كأن يكون لدى مكونات البرنامج بيانات أكثر من المطلوب عن بعضها البعض.
  • يتطلب معالجة خاصة في بيئة متعددة الخيوط (multi-threaded) كي لا تنشئ الخيوط المتعددة كائن مفردة أكثر من مرة.
  • قد يكون من الصعب اختبار شيفرة العميل جزءًا جزءًا - أو على مستوى الوحدة (unit test)- لنمط المفردة بسبب أن العديد من إطارات العمل للاختبارات تعتمد على الاكتساب (inheritance) عند إنتاج كائنات مقلدة (mock objects). وبما أن منشئ فئة المفردةِ خاصًا، ومن المستحيل تخطي الأساليب الساكنة في أغلب اللغات، فإنك في حاجة إلى التفكير في طريقة مبدعة لتقليد المفردة، وإلا فلا تكتب تلك الاختبارات أو تستخدم نمط المفردة على الإطلاق.

العلاقات مع الأنماط الأخرى

  • فئة نمط الواجهة (Facade) قد تتحول إلى مفردة بما أن كائن الواجهة الواحد يكون كافيًا في أغلب الأحوال.
  • قد يتشابه نمط وزن الذبابة (Flyweight) مع نمط المفردة إن استطعت تقليل كل الحالات المشتركة للكائنات إلى كائن واحد من نمط وزن الذبابة، لكن هناك اختلافان رئيسيان بين هذين النمطين:
  1. يجب أن تكون هناك نسخة واحدة من المفردة، بينما يمكن أن يكون لفئة وزن الذبابة أكثر من نسخة بحالات فعلية مختلفة.
  2. يمكن لكائن المفردة أن يكون متقلبًا، أما كائنات نمط وزن الذبابة فتكون ثابتة وغير قابلة للتغيير.

الاستخدام في لغة جافا

المستوى: ★ ☆ ☆

الانتشار: ★ ★ ☆

أمثلة الاستخدام: ينظر كثير من المطورين إلى نمط المفردة على أنه نمط عكسي (antipattern) لهذا يتراجع استخدامه مع الوقت في شيفرة جافا، لكن رغم ذلك فهناك الكثير من الأمثلة لنمط المفردة في مكتبات جافا:

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

مثال: المفردة البسيطة (ذات الخيط الواحد)

من السهل استخدام مفردة غامضة، فما عليك سوى إخفاء المنشئ واستخدام أسلوب إنشاء ساكن.

 Singleton.java: المفردة

package refactoring_guru.singleton.example.non_thread_safe;

public final class Singleton {
    private static Singleton instance;
    public String value;

    private Singleton(String value) {
        // تحاكي الشيفرة التالية عملية التهيئة البطيئة.
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        this.value = value;
    }

    public static Singleton getInstance(String value) {
        if (instance == null) {
            instance = new Singleton(value);
        }
        return instance;
    }
}

 DemoSingleThread.java: شيفرة العميل

package refactoring_guru.singleton.example.non_thread_safe;

public class DemoSingleThread {
    public static void main(String[] args) {
        System.out.println("إذا رأيت نفس القيمة فذلك يعني إعادة استخدام للمفردة" + "\n" +
                "إن رأيت قيمًا مختلفة فيعني ذلك إنشاء مفردتين." + "\n\n" +
                "RESULT:" + "\n");
        Singleton singleton = Singleton.getInstance("FOO");
        Singleton anotherSingleton = Singleton.getInstance("BAR");
        System.out.println(singleton.value);
        System.out.println(anotherSingleton.value);
    }
}

 OutputDemoSingleThread.txt: نتائج التنفيذ

إذا رأيت نفس القيمة فذلك يعني إعادة استخدام للمفردة
إن رأيت قيمًا مختلفة فذلك يعني إنشاء مفردتين

RESULT:

FOO
FOO

مثال: المفردة البسيطة (متعددة الخيوط)

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

 Singleton.java: المفردة

package refactoring_guru.singleton.example.non_thread_safe;

public final class Singleton {
    private static Singleton instance;
    public String value;

    private Singleton(String value) {
        // تحاكي الشيفرة التالية عملية التهيئة البطيئة.
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        this.value = value;
    }

    public static Singleton getInstance(String value) {
        if (instance == null) {
            instance = new Singleton(value);
        }
        return instance;
    }
}

 DemoMultiThread.java: شيفرة العميل

package refactoring_guru.singleton.example.non_thread_safe;

public class DemoMultiThread {
    public static void main(String[] args) {
        System.out.println("إذا رأيت نفس القيمة فذلك يعني إعادة استخدام نفس المفردة" + "\n" +
                "إذا رأيت قيمًا مختلفة فذلك يعني إنشاء مفردتين" + "\n\n" +
                "RESULT:" + "\n");
        Thread threadFoo = new Thread(new ThreadFoo());
        Thread threadBar = new Thread(new ThreadBar());
        threadFoo.start();
        threadBar.start();
    }

    static class ThreadFoo implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("FOO");
            System.out.println(singleton.value);
        }
    }

    static class ThreadBar implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("BAR");
            System.out.println(singleton.value);
        }
    }
}

 OutputDemoMultiThread.txt: نتائج التنفيذ

إذا رأيت نفس القيمة فذلك يعني إعادة استخدام نفس المفردة.
إذا رأيت قيمًا مختلفة فذلك يعني إنشاء مفردتين.

RESULT:

FOO
BAR

مثال: المفردة الصحيحة (آمنة على الخيوط، تحميل كسول)

من أجل إصلاح المشكلة، يجب أن تزامن الخيوط في أول إنشاء لكائن المفردة.

 Singleton.java: المفردة

package refactoring_guru.singleton.example.thread_safe;

public final class Singleton {
    private static volatile Singleton instance;
    public String value;

    private Singleton(String value) {
        this.value = value;
    }

    public static Singleton getInstance(String value) {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(value);
                }
            }
        }
        return instance;
    }
}

 DemoMultiThread.java: شيفرة العميل

package refactoring_guru.singleton.example.thread_safe;

public class DemoMultiThread {
    public static void main(String[] args) {
        System.out.println("إذا رأيت نفس القيمة فذلك يعني إعادة استخدام نفس المفردة" + "\n" +
                "إذا رأيت قيمًا مختلفة فذلك يعني إنشاء مفردتين" + "\n\n" +
                "RESULT:" + "\n");
        Thread threadFoo = new Thread(new ThreadFoo());
        Thread threadBar = new Thread(new ThreadBar());
        threadFoo.start();
        threadBar.start();
    }

    static class ThreadFoo implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("FOO");
            System.out.println(singleton.value);
        }
    }

    static class ThreadBar implements Runnable {
        @Override
        public void run() {
            Singleton singleton = Singleton.getInstance("BAR");
            System.out.println(singleton.value);
        }
    }
}

 OutputDemoMultiThread.txt: نتائج الاستخدام

إذا رأيت نفس القيمة فذلك يعني إعادة استخدام نفس المفردة
إذا رأيت قيمًا مختلفة فذلك يعني إنشاء مفردتين

RESULT:

BAR
BAR

الاستخدام في لغة #C

المستوى: ★ ☆ ☆

الانتشار: ★ ★ ☆

أمثلة الاستخدام: ينظر كثير من المطورين إلى نمط المفردة على أنه نمط عكسي (antipattern) لهذا يتراجع استخدامه مع الوقت في شيفرة #C.

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

المفردة البسيطة Naïve Singlton

من السهل استخدام مفردة غير متقنة، فلا تحتاج إلا إلى إخفاء المنشئ واستخدام أسلوب إنشاء ساكن. وتتصرف نفس الفئة بشكل خاطئ داخل بيئة متعددة الخيوط (multi-threaded)، فبإمكان عدة خيوط أن تستعدي أسلوب الإنشاء في نفس الوقت وتحصل على نسخ متعددة من فئة المفردة.

 Program.cs: مثال تصوري

using System;

namespace Singleton
{
    // الذي يخدم كبديل getInstance تحدد فئة المفردة أسلوب
    // للمنشئ ويسمح للعملاء بالوصول إلى نفس النسخة من الفئة مرة بعد مرة.
    class Singleton
    {
        // خاصًا دائمًا لتجنب Singleton Constructor يجب أن يكون منشئ المفردة
        // new استدعاءات الإنشاء بمعامل.
        private Singleton() { }

        // تُخزن نسخة المفردة في حقل ساكن، وهناك عدة طرق لتهيئة هذا الحقل
        // وكلها لها مزاياها وعيوبها، وسنستعرض في هذا المثال أبسط تلك الطرق.
        // multi-threaded لكنها لن تكون الأنسب لبرنامج متعدد الخيوط.
        private static Singleton _instance;

        // هذا هو الأسلوب الساكن الذي يتحكم في الوصول إلى نسخة المفردة.
        // وهو ينشئ كائن المفردة عند أول تشغيل له ويضعه داخل الحقل الساكن
        // أما في مرات التشغيل التالية فإنه يعيد كائن العميل الموجود مسبقًا
        // Static Field داخل الحقل الساكن.
        public static Singleton GetInstance()
        {
            if (_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }

        // أخيرًا، يجب أن تحدد أي مفردة بعض منطق العمل الذي يمكن
        // تنفيذه على نسختها.
        public static void someBusinessLogic()
        {
            // ...
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // شيفرة العميل.
            Singleton s1 = Singleton.GetInstance();
            Singleton s2 = Singleton.GetInstance();

            if (s1 == s2)
            {
                Console.WriteLine("Singleton works, both variables contain the same instance.");
            }
            else
            {
                Console.WriteLine("Singleton failed, variables contain different instances.");
            }
        }
    }
}

 Output.txt: المخرجات

نمط المفردة يعمل!. كلا المتغيران يحتويان نفس النسخة.

المفردة سليمة الخيوط Thread-safe Singleton

من أجل حل المشكلة فإن عليك مزامنة الخيوط أثناء أول إنشاء لكائن المفردة.

 Program.cs: مثال تصوري

using System;
using System.Threading;

namespace Singleton
{
    // يطلق على هذا الاستخدام من المفردة اسم (القفل ثنائي التحقق)، ذلك
    // أنه آمن في البيئة متعددة الخيوط ويوفر تهيئة بطيئة لكائن المفردة.
    class Singleton
    {
        private Singleton() { }

        private static Singleton _instance;
        // لدينا الآن كائن القفل الذي سيُستخدم لمزامنة الخيوط
        // أثناء أول وصول إلى المفردة.
        private static readonly object _lock = new object();

        public static Singleton GetInstance(string value)
        {
            // هذه الشَّرطية مطلوبة لمنع الخيوط من العثور على القفل
            // بعدما يكتمل تجهيز النسخة.
            if (_instance == null)
            {
                // تخيل الآن أن البرنامج قد تم إطلاقه للتو، تستطيع عدة
                // خيوط الآن أن تعبر الشرطية السابقة بما أنه ليس لدينا
                // نسخة مفردة بعد، وتصل إلى هذه النقطة في نفس الوقت تقريبًا
                // وأول واحد فيها سيحصل على القفل ويتقدم للخطوات التالية، أما
                // البقية فستنتظر هنا.
                lock (_lock)
                {
                    // أول خيط يحصل على القفل ويصل إلى هذه الشرطية يدخل 
                    // لينشئ نسخة من المفردة. وبمجرد أن يترك كتلة القفل
                    // فقد يدخل خيط آخر كان ينتظر تحرير القفل ليدخل هذا
                    // القسم، لكن بما أن حقل المفردة قد هُيئ بالفعل فلن
                    // ينشئ الخيط كائنًا جديدًا.
                    if (_instance == null)
                    {
                        _instance = new Singleton();
                        _instance.Value = value;
                    }
                }
            }
            return _instance;
        }

        // سنستخدم هذه الخاصية لإثبات أن مفردتنا تعمل.
        public string Value { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // شيفرة العميل.
            
            Console.WriteLine(
                "{0}\n{1}\n\n{2}\n",
                "If you see the same value, then singleton was reused (yay!)",
                "If you see different values, then 2 singletons were created (booo!!)",
                "RESULT:"
            );
            
            Thread process1 = new Thread(() =>
            {
                TestSingleton("FOO");
            });
            Thread process2 = new Thread(() =>
            {
                TestSingleton("BAR");
            });
            
            process1.Start();
            process2.Start();
            
            process1.Join();
            process2.Join();
        }
        
        public static void TestSingleton(string value)
        {
            Singleton singleton = Singleton.GetInstance(value);
            Console.WriteLine(singleton.Value);
        } 
    }
}

 Output.txt: نتائج التنفيذ

FOO
FOO

الاستخدام في لغة PHP

المستوى: ★ ☆ ☆

الانتشار: ★ ★ ☆

أمثلة الاستخدام: ينظر كثير من المطورين إلى نمط المفردة على أنه نمط عكسي (antipattern) لهذا يتراجع استخدامه مع الوقت في شيفرة PHP.

مثال: بنية النمط

يوضح هذا المثال بنية نمط المفردة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

سيكون من السهل بعد تعلم بنية النمط أن تستوعب المثال التالي بناءً على مثال واقعي لاستخدام لغة PHP.

 SingletonStructural.php: مثال هيكلي

<?php

namespace RefactoringGuru\Singleton\Structural;

/**
 * الذي يسمح للعملاء بالوصول إلى نسخة getInstance تعرِّف فئة المفردةأسلوب
 * فريدة من المفردة.
 */
class Singleton
{
    private static $instances = [];

    /**
     * يجب أن يكون منشئ المفردة خاصًا دومًا لمنع استدعاءات الإنشاء المباشرة
     * new باستخدام معامل.
     */
    protected function __construct() { }

    /**
     * يجب ألا تكون المفردات قابلة للاستنساخ.
     */
    protected function __clone() { }

    /**
     * (strings) يجب ألا تكون المفردات قابلة للاسترجاع من النصوص.
     */
    public function __wakeup()
    {
        throw new \Exception("Cannot unserialize a singleton.");
    }

    /**
     * الأسلوب الساكن الذي يتحكم في الوصول إلى نسخة المفردة.
     *
     * يسمح لك هذا الاستخدام بتفريع فئة المفردة مع إبقاء نسخة واحدة فقط 
     * من كل فئة فرعية.
     */
    public static function getInstance(): Singleton
    {
        $cls = static::class;
        if (! isset(static::$instances[$cls])) {
            static::$instances[$cls] = new static;
        }

        return static::$instances[$cls];
    }

    /**
     * أخيرًا، يجب أن تعرِّف كلُّ مفردة بعض المنطق التجاري الذي يمكن تنفيذه
     * في نسختها.
     */
    public function someBusinessLogic()
    {
        // ...
    }
}

/**
 * شيفرة العميل.
 */
function clientCode()
{
    $s1 = Singleton::getInstance();
    $s2 = Singleton::getInstance();
    if ($s1 === $s2) {
        echo "نمط المفردة يعمل. كلا المتغيران يحتويان نفس النسخة.";
    } else {
        echo "فشل نمط المفردة. المتغيران يحتويان نسخًا مختلفة";
    }
}

clientCode();

 Output.txt: نتائج التنفيذ

نمط المفردة يعمل. كلا المتغيران يحتويان نفس النسخة.

مثال: حالة حقيقية

يشتهر نمط المفردة أنه يحد من إعادة استخدام الشيفرة وبتعقيد اختبار الوحدات، لكنه لا يزال مفيدًا في بعض الحالات، خاصة حين تريد التحكم في بعض المصادر المشتركة، كحالة كائن تسجيل دخول عام يجب أن يتحكم في الوصول إلى ملف سجل ما، أو سعة تخزين إعدادت وقت تشغيل مشتركة (Shared runtime configuration storage).

 SingletonRealWorld.php: مثال واقعي

<?php

namespace RefactoringGuru\Singleton\RealWorld;

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

/**
 * إن احتجت إلى دعم عدة أنواع من المفردات في تطبيقك فيمكنك تعريف المزايا
 * الأساسية للمفردة في فئة أساسية في حين تنقل المنطق التجاري الفعلي مثل 
 * إلى فئات فرعية logging التسجيل
 */
class Singleton
{
    /**
     * توجد النسخة الفعلية للمفردة داخل حقل ساكن طول الوقت تقريبًا، ويكون 
     * الحقل الساكن في تلك الحالة عبارة عن مصفوفة تسجِّل فيها كل فئة فرعية من 
     * المفردة نسختها الخاصة بها. 
     */
    private static $instances = [];

    /**
     * يجب ألا يكون منشئ المفردة عامًا، لكنه في نفس الوقت لا يمكن أن يكون خاصًا 
     * كذلك إن كنا نرغب بالسماح بإنشاء فئات فرعية.
     */
    protected function __construct() { }

    /**
     * للمفردات (unserialization) لا يسمح بالاستنساخ أو إلغاء التسلسل.
     */
    protected function __clone() { }

    public function __wakeup()
    {
        throw new \Exception("Cannot unserialize singleton");
    }

    /**
     * الأسلوب الذي تستخدمه للحصول على نسخة المفردة.
     */
    public static function getInstance()
    {
        $subclass = static::class;
        if (!isset(self::$instances[$subclass])) {
            // static لاحظ أننا هنا نستخدم الكلمة المفتاحية
            // بدلًا من الاسم الفعلي للفئة، ويُقصد بهذه الكلمة في هذا السياق 
            // اسمَ الفئة الحالية، وذلك التفصيل مهم لأنه عند استدعاء الأسلوب
            // على الفئة الفرعية فإننا نريد إنشاء نسخة من تلك الفئة الفرعية
            // هنا.
           
            self::$instances[$subclass] = new static;
        }
        return self::$instances[$subclass];
    }
}

/**
 * فئة تسجيل الدخول هي أكثر استخدام معروف لنمط المفردة، ففي أغلب الحالات 
 * تحتاج كائن تسجيل دخول وحيد يكتب في ملف سجل واحد، وهذا هو التحكم في مصدر 
 * مشترك. كما تحتاج أيضًا إلى طريقة مناسبة للوصول إلى تلك النسخة من أي سياق 
 * في تطبيقك، وتلك هي نقطة الوصول العامة.
 */
class Logger extends Singleton
{
    /**
     * من ملف السجل file pointer resource مصدر مؤشر ملف
     */
    private $fileHandle;

    /**
     * بما أن منشئ المفردة قد استُدعي مرة واحدة فقط، فإن مصدر ملف واحد فقط
     * قد تم فتحه.
     *
     * console stream لاحظ أنه وبداعي التبسيط، سنفتح سيل المنصة
     * بدلًا من الملف الفعلي هنا.
     */
    protected function __construct()
    {
        $this->fileHandle = fopen('php://stdout', 'w');
    }

    /**
     * الملف المفتوح resource اكتب مُدخل سجل لمصدر 
     */
    public function writeLog(string $message): void
    {
        $date = date('Y-m-d');
        fwrite($this->fileHandle, "$date: $message\n");
    }

    /**
     * مجرد اختصار بسيط لتقليل الشيفرة المطلوبة لتسجيل الرسائل من شيفرة 
     * العميل
     */
    public static function log(string $message): void
    {
        $logger = static::getInstance();
        $logger->writeLog($message);
    }
}

/**
 * يشيع أيضًا تطبيق نمط المفردة على إعدادات التخزين، وغالبًا تحتاج إلى الدخول
 * لإعدادات التطبيق من أماكن كثيرة من البرنامج، ونمط المفردة ييسر هذه 
 * العملية.
 */
class Config extends Singleton
{
    private $hashmap = [];

    public function getValue(string $key): string
    {
        return $this->hashmap[$key];
    }

    public function setValue(string $key, string $value): void
    {
        $this->hashmap[$key] = $value;
    }
}

/**
 * شيفرة العميل.
 */
Logger::log("Started!");

// logger singleton قارن القيَم من.
$l1 = Logger::getInstance();
$l2 = Logger::getInstance();
if ($l1 === $l2) {
    Logger::log("Logger has a single instance.");
} else {
    Logger::log("Loggers are different.");
}

// على البيانات config singleton انظر كيف يحافظ...
$config1 = Config::getInstance();
$login = "test_login";
$password = "test_password";
$config1->setValue("login", $login);
$config1->setValue("password", $password);
// ...ومن ثم يستعيدها.
$config2 = Config::getInstance();
if ($login == $config2->getValue("login") &&
    $password == $config2->getValue("password")
) {
    Logger::log("Config singleton also works fine.");
}

Logger::log("Finished!");

 Output.txt: المخرجات

2018-06-04: Started!
2018-06-04: Logger has a single instance.
2018-06-04: Config singleton also works fine.
2018-06-04: Finished!

الاستخدام في لغة بايثون

المستوى: ★ ☆ ☆

الانتشار: ★ ★ ☆

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

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

المفردة البسيطة Naïve Singlton

من السهل استخدام مفردة غير متقنة، فلا تحتاج إلا إلى إخفاء المنشئ واستخدام أسلوب إنشاء ساكن. وتتصرف نفس الفئة بشكل خاطئ داخل بيئة متعددة الخيوط (multi-threaded)، فبإمكان عدة خيوط أن تستدعي أسلوب الإنشاء في نفس الوقت وتحصل على نسخ متعددة من فئة المفردة.

 main.py: مثال تصوري

from __future__ import annotations
from typing import Optional


class SingletonMeta(type):
    """
    يمكن استخدام فئة المفردة بصور مختلفة في لغة بايثون، من هذه الأساليب
    metaclass والمزخرِف، و base class فئة الأساس.
    لأنها المناسبة لهذا الغرض metaclass وسنستخدم.
    """

    _instance: Optional[Singleton] = None

    def __call__(self) -> Singleton:
        if self._instance is None:
            self._instance = super().__call__()
        return self._instance


class Singleton(metaclass=SingletonMeta):
    def some_business_logic(self):
        """
        أخيرًا، يجب أن تحدد أي مفردة بعض منطق العمل الذي يمكن
        تنفيذه على نسختها.
        """

        # ...


if __name__ == "__main__":
    # شيفرة العميل.

    s1 = Singleton()
    s2 = Singleton()

    if id(s1) == id(s2):
        print("Singleton works, both variables contain the same instance.")
    else:
        print("Singleton failed, variables contain different instances.")

 Output.txt: نتائج التنفيذ

نمط المفردة يعمل. كلا المتغيران يحتويان نفس النسخة.

المفردة التي لا تتأثر بالخيوط Thread-safe Singleton

من أجل حل المشكلة فإن عليك مزامنة الخيوط أثناء أول إنشاء لكائن المفردة.

 main.py: Conceptual Example
from __future__ import annotations
from threading import Lock, Thread
from typing import Optional


class SingletonMeta(type):
    """
    على المفردة thread-safe هذا تطبيق من نوع.
    """

    _instance: Optional[Singleton] = None

    _lock: Lock = Lock()
    """
    سيُستخدم لمزامنة الخيوط في أول وصول للمفردة lock object لدينا الآن كائن إقفال.
    """

    def __call__(cls, *args, **kwargs):
        # والآن، تخيل أن البرنامج قد أُطلِق للتو، تستطيع عدة خيوط
        # أن تمرر الشرطية السابقة في نفس الوقت، وتصل إلى هذه النقطة في
        # نفس الوقت كذلك، بما أنه ليس لدينا نسخة مفردة بعد.
        # ويتقدم لما بعد ذلك، بينما lock وأول خيط منها سيحصل على القفل
        # سينتظر البقية هنا.
        with cls._lock:
            # أول خيط يحصل على القفل ويصل إلى هذه الشرطية يدخل لينشئ نسخة من المفردة.
            # وبمجرد أن يترك كتلة القفل فقد يدخل خيط آخر كان ينتظر تحرير القفل
            # ليدخل هذا القسم. لكن بما أن حقل المفردة قد تم بدؤه بالفعل
            # فلن ينشئ الخيط كائنًا جديدًا.
            if not cls._instance:
                cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class Singleton(metaclass=SingletonMeta):
    value: str = None
    """
    سنستخدم هذه الخاصية لإثبات أن مفردتنا تعمل.
    """

    def __init__(self, value: str) -> None:
        self.value = value

    def some_business_logic(self):
        """
        أخيرًا، يجب أن تحدد أي مفردة بعض منطق العمل الذي يمكن
        تنفيذه على نسختها.
        """


def test_singleton(value: str) -> None:
    singleton = Singleton(value)
    print(singleton.value)


if __name__ == "__main__":
    # شيفرة العميل.

    print("If you see the same value, then singleton was reused (yay!)\n"
          "If you see different values, then 2 singletons were created (booo!!)\n\n"
          "RESULT:\n")

    process1 = Thread(target=test_singleton, args=("FOO",))
    process2 = Thread(target=test_singleton, args=("BAR",))
    process1.start()
    process2.start()

 Output.txt: نتائج التنفيذ

If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

FOO
FOO

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

المستوى: ★ ☆ ☆

الانتشار: ★ ★ ☆

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

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

مثال: بنية النمط

يوضح هذا المثال بنية نمط المفردة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

المفردة البسيطة Naïve Singlton

من السهل استخدام مفردة غير متقنة، فلا تحتاج إلا إلى إخفاء المنشئ واستخدام أسلوب إنشاء ساكن. وتتصرف نفس الفئة بشكل خاطئ داخل بيئة متعددة الخيوط (multi-threaded)، فبإمكان عدة خيوط أن تستعدي أسلوب الإنشاء في نفس الوقت وتحصل على نسخ متعددة من فئة المفردة.

 main.rb: مثال تصوري

# الذي سيسمح للعملاء بالوصول إلى نسخة مفردة فريدة instance تحدد المفردة أسلوب .
class Singleton
  @instance = new

  private_class_method :new
  
  # الأسلوب الساكن الذي يتحكم في الوصول إلى نسخة المفردة.
  #
  # يسمح لك هذا الاستخدام بإنشاء فئة فرعية من فئة المفردة مع الحفاظ
  # على نسخة واحدة فقط من كل فئة فرعية.
  def self.instance
    @instance
  end

  # أخيرًا، يجب أن تحدد أي مفردة بعض منطق العمل الذي يمكن
  # تنفيذه على نسختها.
  def some_business_logic
    # ...
  end
end

# شيفرة العميل.

s1 = Singleton.instance
s2 = Singleton.instance

if s1.equal?(s2)
  print 'Singleton works, both variables contain the same instance.'
else
  print 'Singleton failed, variables contain different instances.'
end

المفردة التي لا تتأثر بالخيوط Thread-safe Singleton

من أجل حل المشكلة فإن عليك مزامنة الخيوط أثناء أول إنشاء لكائن المفردة.

 main.rb: مثال تصوري

# الذي سيسمح للعملاء بالوصول إلى نسخة مفردة فريدة instance تحدد المفردة أسلوب .
class Singleton
  attr_reader :value

  @instance_mutex = Mutex.new

  private_class_method :new

  def initialize(value)
    @value = value
  end

  # الأسلوب الساكن الذي يتحكم في الوصول إلى نسخة المفردة.
  #
  # يسمح لك هذا الاستخدام بإنشاء فئة فرعية من فئة المفردة مع الحفاظ
  # على نسخة واحدة فقط من كل فئة فرعية.
  def self.instance(value)
    return @instance if @instance

    @instance_mutex.synchronize do
      @instance ||= new(value)
    end

    @instance
  end

  # أخيرًا، يجب أن تحدد أي مفردة بعض منطق العمل الذي يمكن
  # تنفيذه على نسختها.
  def some_business_logic
    # ...
  end
end

# @param [String] value
def test_singleton(value)
  singleton = Singleton.instance(value)
  puts singleton.value
end

# شيفرة العميل.

puts "If you see the same value, then singleton was reused (yay!)\n"\
     "If you see different values, then 2 singletons were created (booo!!)\n\n"\
     "RESULT:\n\n"

process1 = Thread.new { test_singleton('FOO') }
process2 = Thread.new { test_singleton('BAR') }
process1.join
process2.join

 output.txt: نتائج التنفيذ

If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)

RESULT:

FOO
FOO

الاستخدام في لغة Swift

المستوى: ★ ☆ ☆

الانتشار: ★ ★ ☆

أمثلة الاستخدام: ينظر كثير من المطورين إلى نمط المفردة على أنه نمط عكسي (antipattern) لهذا يتراجع استخدامه مع الوقت في شيفرة Swift.

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

مثال: بنية النمط

يوضح هذا المثال بنية نمط المفردة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift.

 Example.swift: مثال تصوري

import XCTest

/// الذي يسمح للعملاء بالوصول إلى نسخة فريدة shared تحدد فئة المفردة حقل
/// للمفردة.
class Singleton {

    /// الأسلوب الساكن الذي يتحكم في الوصول إلى نسخة المفردة.
    ///
    /// يسمح لك هذا الاستخدام بإنشاء فئة فرعية من فئة المفردة مع الحفاظ
    /// على نسخة واحدة فقط من كل فئة فرعية.
    static var shared: Singleton = {
        let instance = Singleton()
        // ... اضبط إعدادات النسخة.
        // ...
        return instance
    }()

    /// خاصًا دائمًا لتجنب Singleton Initializer يجب أن يكون مهيئ المفردة 
    /// new استدعاءات الإنشاء بمعامل.
    private init() {}

    /// أخيرًا، يجب أن تحدد أي مفردة بعض منطق العمل الذي يمكن
    /// تنفيذه على نسختها.
    func someBusinessLogic() -> String {
        // ...
        return "Result of the 'someBusinessLogic' call"
    }
}

/// يجب أن تكون المفردات قابلة للاستنساخ.
extension Singleton: NSCopying {

    func copy(with zone: NSZone? = nil) -> Any {
        return self
    }
}

/// شيفرة العميل.
class Client {
    // ...
    static func someClientCode() {
        let instance1 = Singleton.shared
        let instance2 = Singleton.shared

        if (instance1 === instance2) {
            print("Singleton works, both variables contain the same instance.");
        } else {
            print("Singleton failed, variables contain different instances.");
        }
    }
    // ...
}

/// لنرى كيف سيعمل كل ذلك.
class SingletonConceptual: XCTestCase {

    func testSingletonConceptual() {
        Client.someClientCode();
    }
}

 Output.txt: نتائج التنفيذ

نمط المفردة يعمل. كلا المتغيران يحتويان نفس النسخة.

مثال واقعي

 Example.swift: مثال واقعي

import XCTest

/// نمط تصميم المفردة.
///
/// الهدف: ضمان أن الفئة لديها نسخة واحدة فقط، وتوفير نقطة وصول عامة لها.

class SingletonRealWorld: XCTestCase {

    func testSingletonRealWorld() {

        /// View Controllers هناك اثنين من متحكمات العرض
        ///
        /// قائمة من آخر الرسائل من محادثة المستخدم MessagesListVC يعرض.
        /// محادثة مع صديق ChatVC يعرض.
        ///
        /// الرسائل من خادم وتوفر الرسائل القديمة FriendsChatService تجلب خدمة 
        /// والجديدة لكل المشتركين.
        ///
        /// إذ قد تُستخدم كنسخة من فئة أو ، FriendsChatService يستخدم كلا المتحكميْن
        /// متغيرًا عامًا.
        ///
        /// من المهم في هذا المثال أن تكون هناك نسخة واحدة فقط تنفذ
        /// المهام التي تستهلك الموارد.

        let listVC = MessagesListVC()
        let chatVC = ChatVC()

        listVC.startReceiveMessages()
        chatVC.startReceiveMessages()

        /// ...(Navigation Stack) واعرض المتحكمات لمكدس التنقل ...
    }
}


class BaseVC: UIViewController, MessageSubscriber {

    func accept(new messages: [Message]) {
        /// (Base Class) يعالج الرسائل الجديدة في الفئة الأساسية
    }

    func accept(removed messages: [Message]) {
        /// (Base Class) يعالج الرسائل المحذوفة في الفئة الأساسية
    }

    func startReceiveMessages() {

        /// لكن من منظور ، (dependency) يمكن حقن المفردة كاعتمادية
        /// معلوماتي فإن هذا المثال
        /// مباشرة لتوضيح هدف النمط FriendsChatService يستدعي
        /// وهو توفير نقطة وصول عامة إلى النسخة.

        FriendsChatService.shared.add(subscriber: self)
    }
}

class MessagesListVC: BaseVC {

    override func accept(new messages: [Message]) {
        print("MessagesListVC accepted 'new messages'")
        /// (child Class) يعالج الرسائل الجديدة في الفئة الفرعية
    }

    override func accept(removed messages: [Message]) {
        print("MessagesListVC accepted 'removed messages'")
        ///  (child Class) يعالج الرسائل المحذوفة في الفئة الفرعية
    }

    override func startReceiveMessages() {
        print("MessagesListVC starts receive messages")
        super.startReceiveMessages()
    }
}

class ChatVC: BaseVC {

    override func accept(new messages: [Message]) {
        print("ChatVC accepted 'new messages'")
        /// (child Class) يعالج الرسائل الجديدة في الفئة الفرعية
    }

    override func accept(removed messages: [Message]) {
        print("ChatVC accepted 'removed messages'")
        /// (child Class) يعالج الرسائل المحذوفة في الفئة الفرعية
    }

    override func startReceiveMessages() {
        print("ChatVC starts receive messages")
        super.startReceiveMessages()
    }
}

/// call-back بروتوكول لكل أحداث.

protocol MessageSubscriber {

    func accept(new messages: [Message])
    func accept(removed messages: [Message])
}

/// بروتوكول للتواصل بخدمة رسائل.

protocol MessageService {

    func add(subscriber: MessageSubscriber)
}

/// Message Domain Model نموذج نطاق الرسالة.

struct Message {

    let id: Int
    let text: String
}


class FriendsChatService: MessageService {

    static let shared = FriendsChatService()

    private var subscribers = [MessageSubscriber]()

    func add(subscriber: MessageSubscriber) {

        /// تبدأ عملية الجلب مرة أخرى في هذا المثال من خلال إضافة مشترك جديد.
        subscribers.append(subscriber)

        /// لاحظ أن المشترك الأول سيستقبل رسائل مرة أخرى حين يضاف المشترك الثاني.
        startFetching()
    }

    func startFetching() {

        /// أعِدَّ مكدس الشبكة، وحقق اتصالًا...
        /// ...واسترجع البيانات من الخادم.

        let newMessages = [Message(id: 0, text: "Text0"),
                           Message(id: 5, text: "Text5"),
                           Message(id: 10, text: "Text10")]

        let removedMessages = [Message(id: 1, text: "Text0")]

        /// أرسل البيانات المُحدَّثة إلى المشتركين.
        receivedNew(messages: newMessages)
        receivedRemoved(messages: removedMessages)
    }
}

private extension FriendsChatService {

    func receivedNew(messages: [Message]) {

        subscribers.forEach { item in
            item.accept(new: messages)
        }
    }

    func receivedRemoved(messages: [Message]) {

        subscribers.forEach { item in
            item.accept(removed: messages)
        }
    }
}

 Output.txt: نتائج التنفيذ

MessagesListVC starts receive messages
MessagesListVC accepted 'new messages'
MessagesListVC accepted 'removed messages'
======== At this point, the second subscriber is added ======
ChatVC starts receive messages
MessagesListVC accepted 'new messages'
ChatVC accepted 'new messages'
MessagesListVC accepted 'removed messages'
ChatVC accepted 'removed messages'

الاستخدام في لغة TypeScript

المستوى: ★ ☆ ☆

الانتشار: ★ ★ ☆

أمثلة الاستخدام: ينظر كثير من المطورين إلى نمط المفردة على أنه نمط عكسي (antipattern) لهذا يتراجع استخدامه مع الوقت في شيفرة TypeScript.

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

مثال: بنية النمط

يوضح هذا المثال بنية نمط المفردة، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 index.ts: مثال تصوري

/**
 * الذي يسمح للعملاء بالوصول إلى نسخة فريدة getInstance تحدد فئة المفردة حقل
 * للمفردة.
 */
class Singleton {
    private static instance: Singleton;

    /**
     * خاصًا دائمًا لتجنب Singleton Constructor يجب أن يكون منشئ المفردة
     * new استدعاءات الإنشاء بمعامل.
     */
    private constructor() { }

    /**
     * الأسلوب الساكن الذي يتحكم في الوصول إلى نسخة المفردة.
     *
     * يسمح لك هذا الاستخدام بإنشاء فئة فرعية من فئة المفردة مع الحفاظ
     * على نسخة واحدة فقط من كل فئة فرعية.
     */
    public static getInstance(): Singleton {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }

        return Singleton.instance;
    }

    /**
     * أخيرًا، يجب أن تحدد أي مفردة بعض منطق العمل الذي يمكن
     * تنفيذه على نسختها.
     */
    public someBusinessLogic() {
        // ...
    }
}

/**
 * شيفرة العميل.
 */
function clientCode() {
    const s1 = Singleton.getInstance();
    const s2 = Singleton.getInstance();

    if (s1 === s2) {
        console.log('Singleton works, both variables contain the same instance.');
    } else {
        console.log('Singleton failed, variables contain different instances.');
    }
}

clientCode();

 Output.txt: نتائج التنفيذ

نمط المفردة يعمل. كلا المتغيران يحتويان نفس النسخة.

انظر أيضًا

مصادر