متى يجب عليك استخدام النمط المفرد بدلاً من الفصل الثابت؟[مغلق]

StackOverflow https://stackoverflow.com/questions/46541

  •  09-06-2019
  •  | 
  •  

سؤال

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

هل كانت مفيدة؟

المحلول

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

نصائح أخرى

ماذا عن "تجنب كليهما"؟المفردات والفئات الثابتة:

  • قد يقدم حالة عالمية
  • اقترن بإحكام بفئات أخرى متعددة
  • إخفاء التبعيات
  • يمكن أن يجعل دروس اختبار الوحدة في عزلة أمرًا صعبًا

بدلا من ذلك، النظر في حقن التبعية و انقلاب حاوية التحكم المكتبات.ستتولى العديد من مكتبات IoC إدارة مدى الحياة نيابةً عنك.

(كما هو الحال دائمًا، هناك استثناءات، مثل فئات الرياضيات الثابتة وأساليب تمديد C#.)

أنا أزعم أن الاختلاف الوحيد هو بناء الجملة:MySingleton.Current.Whatever() مقابل MySingleton.Whatever().والدولة، كما ذكر ديفيد، هي في نهاية المطاف "ثابتة" في كلتا الحالتين.


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

واحدة من المناقشات المفضلة لدي حول هذه القضية هي هنا (الموقع الأصلي معطل، مرتبط الآن بـ آلة Wayback أرشيف الإنترنت.)

لتلخيص مزايا المرونة في Singleton:

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

تعتبر الفئة الثابتة التي تحتوي على عدد كبير من المتغيرات الثابتة نوعًا من الاختراق.

/**
 * Grotty static semaphore
 **/
 public static class Ugly {

   private static int count;

   public synchronized static void increment(){
        count++;
   }

   public synchronized static void decrement(){
        count--;
        if( count<0 ) {
            count=0;
        }
   }

   public synchronized static boolean isClear(){
         return count==0;    

    }
   }

المفرد مع مثيل فعلي أفضل.

/**
 * Grotty static semaphore
 **/
 public static class LessUgly {
   private static LessUgly instance;

   private int count;

   private LessUgly(){
   }

   public static synchronized getInstance(){
     if( instance==null){
        instance = new LessUgly();
     }
     return instance;
   }
   public synchronized void increment(){
        count++;
   }

   public synchronized void decrement(){
        count--;
        if( count<0 ) {
            count=0;
        }
   }

   public synchronized boolean isClear(){
         return count==0;    

    }
   }

الدولة موجودة فقط في المثال.

لذلك يمكن تعديل المفردة لاحقًا للقيام بالتجميع والمثيلات المحلية لمؤشر الترابط وما إلى ذلك.ولا يحتاج أي من التعليمات البرمجية المكتوبة بالفعل إلى التغيير للحصول على الفائدة.

public static class LessUgly {
       private static Hashtable<String,LessUgly> session;
       private static FIFO<LessUgly> freePool = new FIFO<LessUgly>();
       private static final POOL_SIZE=5;
       private int count;

       private LessUgly(){
       }

       public static synchronized getInstance(){
         if( session==null){
            session = new Hashtable<String,LessUgly>(POOL_SIZE);
            for( int i=0; i < POOL_SIZE; i++){
               LessUgly instance = new LessUgly();  
               freePool.add( instance)
            }
         }
         LessUgly instance = session.get( Session.getSessionID());
         if( instance == null){
            instance = freePool.read();
         }
         if( instance==null){
             // TODO search sessions for expired ones. Return spares to the freePool. 
             //FIXME took too long to write example in blog editor.
         }
         return instance;
       }     

من الممكن القيام بشيء مماثل مع فئة ثابتة ولكن سيكون هناك حمل إضافي لكل مكالمة في الإرسال غير المباشر.

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

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

فكر في فرد واحد مثل الخدمة.إنه كائن يوفر مجموعة محددة من الوظائف.على سبيل المثال

ObjectFactory.getInstance().makeObject();

مصنع الكائنات هو كائن يؤدي خدمة محددة.

على النقيض من ذلك، فإن الفصل المليء بالطرق الثابتة هو عبارة عن مجموعة من الإجراءات التي قد ترغب في تنفيذها، ويتم تنظيمها في مجموعة ذات صلة (الفصل).على سبيل المثال

StringUtils.reverseString("Hello");
StringUtils.concat("Hello", "World");

مثال StringUtils هنا عبارة عن مجموعة من الوظائف التي يمكن تطبيقها في أي مكان.كائن المصنع المفرد هو نوع محدد من الكائنات له مسؤولية واضحة يمكن إنشاؤها وتمريرها عند الحاجة.

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

لا ينبغي استخدام المفردات بنفس طريقة استخدام الفئات الثابتة.في الجوهر،

MyStaticClass.GetInstance().DoSomething();

هو في الأساس نفس

MyStaticClass.DoSomething();

ما يجب عليك فعله في الواقع هو التعامل مع المفرد باعتباره مجرد كائن آخر.إذا كانت الخدمة تتطلب مثيلًا من النوع المفرد، فقم بتمرير هذا المثيل في المُنشئ:

var svc = new MyComplexServce(MyStaticClass.GetInstance());

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

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

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

MySingleton().getInstance().doSomething();

عكس

MySingleton.doSomething();

من الواضح أن الأجزاء الداخلية لـ MySingleton ستكون مختلفة فيما بينها، ولكن بغض النظر عن مشكلات سلامة الخيط، فإن كلاهما سيؤديان نفس الشيء فيما يتعلق برمز العميل.

يُستخدم النمط المفرد عمومًا لخدمة مثيلات البيانات المستقلة أو الثابتة حيث يمكن لخيوط متعددة الوصول إلى البيانات في نفس الوقت.أحد الأمثلة يمكن أن يكون رموز الدولة.

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

يوضح أي مثال على المفردة إلى حد كبير كيفية عدم القيام بذلك.

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

يبدو رمز التنظيف أكثر طبيعية عندما يكون لديك مفرد، بدلاً من فئة ثابتة تحتوي على حقول حالة ثابتة.

ومع ذلك، سيبدو الرمز متماثلًا في كلتا الحالتين، لذا إذا كانت لديك أسباب أكثر تحديدًا للسؤال، فربما يتعين عليك توضيح ذلك.

يمكن أن يكون الاثنان متشابهين تمامًا، لكن تذكر أن المفرد الحقيقي يجب أن يكون كذلك بحد ذاتها يتم إنشاء مثيل له (يُمنح مرة واحدة) ثم يتم تقديمه.فئة قاعدة بيانات PHP التي تُرجع مثيلاً لـ mysqli ليس Singleton حقًا (كما يسميه البعض)، لأنه يُرجع مثيلًا لفئة أخرى، وليس مثيلًا للفئة التي تحتوي على المثيل كعضو ثابت.

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

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

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

روبية

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

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

Foo foo = Foo.getInstance();
doSomeWork(foo); // doSomeWork wont even know Foo is a singleton

من الواضح أن هذا يجعل الأمور أسهل عندما تختار التخلص من نمط Singleton لصالح نمط حقيقي، مثل IoC.

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

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

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

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

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

ومع ذلك، مثلما يمكن إنشاء Singleton باستخدام بعض المتغيرات الثابتة، فقد تتمكن من مقارنتها بـ "goto".يمكن أن يكون مفيدًا جدًا لبناء هياكل أخرى، لكنك تحتاج حقًا إلى معرفة كيفية استخدامه ويجب عدم "الإفراط في استخدامه".لذلك فإن التوصية العامة هي الالتزام بـ Singleton، واستخدام static إذا كان عليك ذلك.

تحقق أيضًا من المنشور الآخر: لماذا تختار فئة ثابتة على تطبيق فردي؟

يشير إلى هذا

ملخص:

أ.إحدى القواعد الأساسية السهلة التي يمكنك اتباعها هي أنه إذا لم تكن بحاجة إلى الحفاظ على الحالة، فيمكنك استخدام فئة ثابتة، وإلا فيجب عليك استخدام فئة Singleton.

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

عندما يحتاج الفصل الواحد إلى الحالة.تحافظ المفردات على حالة عالمية، بينما لا تفعل ذلك الفئات الثابتة.

على سبيل المثال، إنشاء مساعد حول فئة التسجيل:إذا كان لديك خلية قابلة للتغيير (HKey Current User vs.HKEY Local Machine) يمكنك الذهاب إلى:

RegistryEditor editor = RegistryEditor.GetInstance();
editor.Hive = LocalMachine

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top