English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
هذا المقال يشرح مثالًا على نموذج Singleton في تصميم البرمجة لـ Android. يُشارك مع الجميع للمراجعة، كما يلي:
التعريف
نموذج Singleton هو واحد من أكثر الأنماط استخدامًا، وربما يكون النموذج الوحيد الذي يستخدمه المهندسون المبتدئون. عند تطبيق هذا النموذج، يجب على فئة الكائن الفردي التأكد من وجود كائن واحد فقط. في كثير من الأحيان، يحتاج النظام إلى كائن عالمي واحد، مما يساعدنا على تنسيق سلوك النظام ككل.
التعريف
تأكد من أن هناك كائن واحد فقط من نوع معين، وأنه يتم إنشاؤه تلقائيًا ويقدم هذا الكائن لكل النظام.
الساحة الاستخدام
تأكد من أن هناك كائن واحد فقط من نوع معين، مما يمنع من إنشاء كائنات متعددة تستهلك موارد كثيرة، أو أن نوعًا معينًا من الكائنات يجب أن يكون له كائن واحد فقط. على سبيل المثال، إذا كانت تكلفة إنشاء الكائن تستهلك موارد كبيرة، مثل الوصول إلى IO وقاعدة البيانات، يجب النظر في استخدام نموذج Singleton.
الطريقة التنفيذية
1، نموذج الجائع
مثال على الكود:
/** * نموذج الجائع */ public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); {} return instance; {} {}
القوة:تحميل متأخر (تحميل عند الحاجة)
العيوب:غير آمن في السلاسل المتعددة، يسهل ظهور حالات عدم التوازن في الحالات مثل العمليات الكثيرة في القراءة والكتابة على قاعدة البيانات.
2، نموذج الجائع
مثال على الكود:
/** * نموذج الجائع */ public class Singleton { private static Singleton instance; private Singleton(){} public static synchronized Singleton getInstance() if(instance == null){ instance = new Singleton(); {} return instance; {} {}
على عكس نموذج الجائع، تم إضافة كلمة المفتاح synchronized في طريقة getInstance، مما يعني أن getInstance هي طريقة متزامنة، مما يضمن وحدة وجود الكائن الفردي في حالة وجود متعددة السلاسل، ولكن إذا فكرنا في ذلك، قد نكتشف مشكلة، حتى إذا تم تتبع instance (سيتم تتبع instance في أول استدعاء)، سيتم دائمًا تنفيذ التزامن في كل مرة يتم فيها استدعاء طريقة getInstance، مما يؤدي إلى استهلاك موارد غير ضرورية، وهذا هو أكبر مشكلة لنموذج الجائع.
القوةيتم حل مشكلة عدم الأمان في الاتصال.
العيوبفي أول تحميل يجب أن يتم التشغيل بشكل سريع، مشكلة كبيرة هي أن كل مرة يتم استدعاء getInstance يتم فيها التزامن، مما يؤدي إلى تكاليف التزامن غير الضرورية.
إضافاتفي مصدر Android، هناك طرق يستخدم هذا النموذج للعينة مثل InputMethodManager، AccessibilityManager، إلخ.
3- DCL (Double Check Lock)
مثال على الكود:
/** * نموذج DCL (Double Check Lock) للعينة */ public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class) { if(instance == null) { instance = new Singleton(); {} {} {} return instance; {} {}
النقطة البارزة في هذا البرنامج طبيعيًا هي طريقة getInstance، يمكن رؤية أن getInstance تقوم بتحقق من instance مرتين: الطبقة الأولى من التحقق هي لتجنب التزامن غير الضروري، والطبقة الثانية من التحقق هي لإنشاء مثيل عند عدم وجود null.
افترض أن thread A يتم تنفيذه إلى جملة instance = new Singleton()، يبدو أن هذا جملة واحدة، لكن في الواقع إنها ليست عملية إتمامية، هذه الجملة ستتم ترجمتها إلى العديد من أوامر汇编، و تقوم بشكل عام بثلاث مهام:
(1) تخصيص ذاكرة Singleton;
(2) استدعاء مبدأ Singleton()، وتحديد حقل العضوية;
(3) إعادة تشير instance إلى مساحة الذاكرة المخصصة (عند هذا الوقت لم يعد instance null).
لكن، بسبب أن محول Java يسمح بتنفيذ غير متسلسل، و بسبب أن JDK1.5 قبل ذلك لم يتم تحديد ترتيب كتابة Cache و register إلى الذاكرة الرئيسية في JMM (Java Memory Model، أي نموذج ذاكرة Java)، فإن ترتيب الجملة الثانية والثالثة في النص المذكور غير مضمون. بمعنى آخر، يمكن أن يكون الترتيب تنفيذي 1-2-3 أو 1-3-2. إذا كان الأخير، و إذا تم التبديل إلى thread B قبل إكمال الخطوة 3 و لم يتم تنفيذ الخطوة 2، فإن instance لأنها تم تنفيذ النقطة الثالثة في thread A أصبحت غير فارغة، لذا فإن thread B قام بأخذ instance مباشرة، و عند استخدامه مرة أخرى سيحدث خطأ، وهذا هو مشكلة فشل DCL، و يمكن أن يكون هذا الخطأ الصعب المتتبع والاستنساخ مخفيًا لفترة طويلة.
بعد JDK 1.5، لاحظت SUN رسميًا هذه المشكلة، وأعدلت JVM، وأصبحت كلمة volatile محددة، لذا إذا كان JDK هو 1.5 أو أحدث، فإن تعديل تعريف instance إلى private volatile static Singleton instance يضمن أن يتم قراءة object instance من ذاكرة النظام الرئيسية في كل مرة، مما يمكن استخدام كتابة DCL لإنشاء نموذج التكرار الواحد. بالطبع، قد يؤثر volatility على الأداء إلى حد ما، ولكن من أجل صحة البرنامج، يبدو أن التضحية بهذا الأداء يستحق.
القوةالاستخدام الكامل للموارد، عند تنفيذ getInstance لأول مرة يتم إنشاء نموذج التكرار الواحد، مما يزيد من الكفاءة. قد يعمل نموذج التكرار الواحد بشكل مثالي في سيناريوهات لا تزال فيها الكثافة المنخفضة وسلامة التوازي منخفضة.
العيوبالتحميل الأول قد يكون بطيئًا، وربما يفشل نتيجة لذاكرة Java أيضًا. هناك أيضًا عيوب في بيئات التوازي، على الرغم من أن فرصتها صغيرة.
إضافاتفي مشروع Android المفتوح المصدر للصور Android-Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader) يتم استخدام هذا النوع.
نموذج DCL هو طريقة تنفيذ التكرار الواحد الأكثر استخدامًا، حيث يمكنه إنشاء نموذج التكرار الواحد عند الحاجة، ويمكنه ضمان استقلالية نموذج التكرار الواحد في معظم السيناريوهات، ما لم يكن لديك كود معقد في سيناريوهات التوازي أو استخدام إصدار JDK أقل من 6، فغالبًا ما يمكن أن يفي بهذا النوع من الحاجة.
4、نموذج التكرار الواحد للفئة الداخلية الثابتة
DCL على الرغم من أن حل بعض المشاكل مثل استهلاك الموارد، التزامن الزائد، والأمان في التشابك، إلا أنه لا يزال يظهر مشكلة الفشل في بعض الحالات. هذه المشكلة تسمى DCL (فشل التحقق المزدوج)، وتتحدث كتاب "ممارسات البرمجة المتوازية في Java" في نهاية الكتاب عن هذه المشكلة، ويشير إلى أن هذا "تحسين" قبيح، ويُنصح بعدم استخدامه. ويُنصح باستبدال الكود التالي:
مثال على الكود:
/** * نموذج التكرار الواحد للفئة الداخلية الثابتة */ public class Singleton { private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.instance; {} /** * فئة داخلية ثابتة * تأخير التحميل، تقليل استهلاك الذاكرة */ private static class SingletonHolder{ private static final Singleton instance = new Singleton(); {} {}
عند تحميل Singleton بشكل أولي لن يتم تحميل instance، فقط عند استدعاء طريقة getInstance لـ Singleton سيؤدي إلى تحميل instance. لذلك، سيؤدي استدعاء طريقة getInstance لأول مرة إلى تحميل SingletonHolder، مما يؤمن بالأمان التوازي، وكذلك بالفردية لنموذج Singleton، وكذلك تأخير إنشاء Singleton، لذا هذا هو نموذج Singleton الموصى به لتنفيذ.
القوة:تحميل متأخر، آمن من حيث التوازي (في Java، يتم تحميل الفئات بشكل منفصل)، مما يقلل من استهلاك الذاكرة
5. Singleton باستخدام enum
تم شرح بعض طرق تنفيذ نموذج Singleton المختلفة، ولكن هذه الطرق ليست معقدة أو قد تسبب مشاكل في بعض الحالات.
مثال على الكود:
/** * نموذج Singleton باستخدام enum; */ public enum Singleton { /** * 1. بدءًا من Java 1.5; * 2. تقديم ميكانيكية التسلسل بشكل مجاني; * 3. منع التكرار المتكرر لإنشاء النموذج، حتى في مواجهة هجمات التسلسل المعقدة أو الت反射; */ instance; private String others; Singleton() { {} public String getOthers() { return others; {} public void setOthers(String others) { this.others = others; {} {}
سهولة الكتابة هي أكبر ميزة لـ enum Singleton، حيث أن enum في Java مشابه تمامًا للفئات العادية، ليس فقط يمكن له أن يحتوي على حقول، بل يمكنه أيضًا أن يحتوي على طرق خاصة. والأهم من ذلك، يتم إنشاء نموذج enum Singleton بشكل افتراضي بشكل آمن من حيث التوازي، وهو دائمًا نموذج Singleton في أي حالة.
لماذا قلنا ذلك؟ في أنواع تنفيذ نموذج الحالة الخاصة بالعينة المذكورة أعلاه، يحدث إنشاء نموذج جديد في حالة واحدة فقط، وهي التسلسل.
من خلال التسلسل يمكن كتابة نموذج الحالة الخاصة بالعينة إلى قرص الصلب، ومن ثم قراءتها مرة أخرى، مما يؤدي إلى الحصول على نموذج فعال. حتى إذا كان بناء النموذج خاصًا، يمكن إنشاء نموذج جديد للفئة من خلال وسيلة خاصة أثناء التسلسل، مما يعادل دعوة بناء النموذج للفئة. يقدم عملية التسلسل عملاً خاصًا يسمى hook function، يحتوي على طريقة خاصة وسرية ومحولة إلى النموذج، تتيح للمطورين التحكم في تسلسل النموذج. على سبيل المثال، إذا كان يجب منع إنشاء نموذج جديد للعينة عند التسلسل، يجب إضافة الطريقة التالية:
private Object readResolve() throws ObjectStreamException { return instance; {}
يعني إرجاع كائن instance في طريقة readResolve بدلاً من إنشاء كائن جديد بشكل افتراضي. أما بالنسبة للقائمة، فإن هذا المشكلة غير موجودة لأنها لن تنشئ كائنًا جديدًا أثناء التشغيل.
القوةيقدم ميكانيزم التسلسل مجانًا، ويمنع التكرار المزدوج للتشغيل، حتى في مواجهة هجمات التسلسل أو الت反射.
العيوببدءًا من Java1.5.
تم استعراض خمس طرق لإنشاء نموذج Singleton في الجزء السابق، يمكن للجميع استخدامها بناءً على نقاط القوة والضعف الخاصة بها في مشاريعهم الفعلية.
يمكن للمقارئين المهتمين بمرور المزيد حول محتوى Android التحقق من مواضيع هذا الموقع: 'تدريب بداية وتقدم تطوير Android'، 'خبرات الت调试 والتغلب على المشاكل الشائعة في Android'، 'تحليل استخدامات المكونات الأساسية في Android'، 'تحليل تقنيات عرض Android'، 'تحليل تقنيات ترتيب Android'، و 'تحليل استخدامات التحكمات في Android'.
نتمنى أن يكون هذا المقال قد ساعد الجميع في تصميم برمجيات Android.
إعلان: محتويات هذا المقال تم جمعها من الإنترنت، حقوق الطبع والنشر تخص مالكها، تم جمع المحتويات بواسطة المستخدمين عبر الإنترنت وتحميلها تلقائيًا، هذا الموقع لا يملك حقوق الملكية، ولم يتم تعديل المحتويات يدويًا ولا يتحمل المسؤولية القانونية المتعلقة بذلك. إذا لاحظت محتويات تتضمن انتهاكًا لحقوق النسخ، فيرجى إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (يرجى استبدال #بـ @ عند إرسال البريد الإلكتروني) لإبلاغنا، وقدم الدليل على ذلك، إذا تم التحقق من صحة المعلومات، سيتم حذف المحتويات المشبوهة فورًا.