أنماط التصميم في الجافا

لغة الجافا من أكثر لغات البرمجة تطوراً وأكبرها إنتشاراً وشهرة, ويمكن القول بإنها تكنولوجيا كاملة لا مجرد لغة برمجة فيمكن إستخدامها في العديد من المنصات وأنظمة التشغيل المختلفة والنظم المدمجة وغيرها ويمكن تخصيص آلة الجافا الإفتراضية (JVM) لتناسب بيئة التشغيل المعينة التي نريد تشغيل جافا عليها مثل ما فعلت شركة قوقل بتخصيص ال JVM لبيئة الهواتف المحمولة في نظامهم الأندرويد وأطلقوا عليها Dalvik virtual machine , إذا فلغة جافا لغة مرنة جداً ويمكن تشغيلها في بيئات مختلفة ,, ولكن ينبغي على مبرمج من مبرمجي الجافا أن يكون على دراية بالطرق المناسبة لكتابة الكود البرمجي حتى لا تحدث المشاكل التي لا تُحمد عقباها.

ما هي أنماط التصميم ؟

نمط التصميم هو أفضل طريقة لحل مشكلة معينة تواجه كل مبرمج يستخدم لغة جافا, ويُتوصَّل إليها بالخبرات والتجارب

ويمكن تقسيم أنماط التصميم إلى ثلاث مجموعات تندرج تحت كل منها عدد من الأنماط :

1. نمط الإنشاء (Creational design pattern) :

هو الذي يهتم بإيجاد حلول لمشاكل إنشاء ال object بأفضل طريقة ممكنة بمعنى أنه ليس من المناسب إنشاء ال object بإستخدام الكلمة المحجوزة new في أي حالة أن يكون ذلك مناسباً فقد تحتاج في بعض الحالات أن تخفى طريقة الإنشاء من كل من يستخدم ال class الذي قمت بإنشاءه , وأيضاً في بعض الحالات ليس من المناسب أن تقوم بإنشاء object من ال class الذي تريد الوصول إلى وظائفه في كل مرة تنشئ object فقد يؤثر ذلك في الأداء. تندرج تحت هذا النمط 5 طرق وهي :

   1. نمط Singleton

   2. نمط Factory

   3. نمطAbstract factory

   4. نمط Builder

   5. نمط Prototype

2. نمط الهيكلة (Structural design pattern ):

هي مجموعة الأنماط التي تهتم بتوفير حلول مختلفة لإنشاء هيكل ال class . وتندرج تحت هذا النمط 7 طرق وهي :

   1. نمط Adapter

   2. نمط Composite

   3. نمط Proxy

   4. نمط Flyweight

   5. نمط Facade

   6. نمط Bridge

   7. نمط Decorator

3. نمط السلوك (Behavioral design pattern):

هي مجموعة الأنماط التي تهتم بتوفير حلول لأفضل طرق العلاقات بين ال objects . وتندرج تحت هذا النمط 11 طريقة وهي :

   1. نمط Template method

   2. نمط Mediator

   3. نمط Chain of responsibility

   4. نمط Observer

   5. نمط Strategy

   6. نمط Command

   7. نمط State

   8. نمط Visitor

   9. نمط Interpreter

   10. نمط Iterator

   11. نمط Memento

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

نمط Singleton :

تعريفه : هو نمط يستخدم لإنشاء object واحد فقط من ال class يعني في كل مرة تريد الوصول لأي وظيفة من وظائف ال class يتم إستخدام نفس ال object المحجوز في الذاكرة ولا يتم إنشاء واحد جديد. وهذا النمط يمنع إنشاء ال object مباشرة بأن يقوم بعمل private constructor لل class

متى يستخدم ؟ يستخدم في حالات كثيرة منها logs و ال driver object مثل محركات قواعد البيانات وأيضاً في cashing وفي Thread Pool وغيرها كثير

ولكي تقوم بتطبيق هذا النمط يجب عليك مراعاة الآتي :

   • ال constructor يجب أن يكون من نوع private حتى يمنع إنشاء ال object بطريقة مباشرة من خارج ال class بإستخدام الكلمة المحجوزة new

   • متغير من نوع private static من نفس ال class والذي يتم عن طريقه إنشاء ال object من ال class

   • دالة (method ) من نوع public static تقوم بإرجاع ال object من ال class حتى يتم الوصول إليها من أي class آخر

المشاكل التي تم حلها بإستخدام هذا النمط :

الطرق التي يمكن بها تطبيق نمط ال Singleton كثيرة وهي :

1. Eager Initialization :

في هذه الطريقة يتم إنشاء ال object في داخل جسم الكلاس وتقوم ال JVM بتحميله وإنشائه عند تحميل الكلاس بواسطة ال class loader

مثال

public class EagerSingletonExample {
	private static final EagerSingletonExample instance = new EagerSingletonExample();

	private EagerSingletonExample(){}

	public static EagerSingletonExample getInstance() {
		return instance;
        }
}

ولكن هذه الطريقة فيها بعض القصور وهي أنه يتم إنشاء ال object عندما يقوم ال JVM بتحميل الكلاس بواسطة ال class loader وحتى ولو لم يستخدم أحد هذا ال object يكون قد تم حجز مكان له في JVM

2. Static block initialization :

يشبه الطريقة السابقة ولكنه يقوم بإنشاء ال object في static block مما يسهل إضافة خيار معالجة الإستثناءات (exception handling)

public class StaticBlockSingletonExample {
	private static StaticBlockSingletonExample instance;

	private StaticBlockSingletonExample(){}

	static {
		try {
			instance = new StaticBlockSingletonExample();
                }catch(Exception e){
                 }
        }
 public static StaticBlockSingletonExample getInstance(){
	return instance;
 }
}

وأيضاً هذه الطريقة في نفس المشكلة السابقة وهي أنه يتم إنشاء ال object حتى لو لم تقم بمناداة الدالة getInstance وذلك لأن عملية الإنشاء تمت في داخل جسم الكلاس وستقوم ال JVM بتنفيذه عند تحميل الكلاس بواسطة ال class loader

3. Lazy Initialization :

يتم فيها إنشاء ال object داخل الدالة getInstance نفسها وهذه الطريقة تحل المشكلة السابقة

مثال لها

public class LazySingletonExample {
	private static LazySingletonExample instance ;
	private LazySingletonExample(){}
	public static LazySingletonExample getInstance(){
		if(instance == null)
			instance  = new LazySingletonExample();

		return instance ;
        }
}

هذه الطريقة ممتازة ولكن تظهر فيها المشاكل في الأنظمة متعددة العمليات multithreaded systems بالتحديد في حالة وصول أكثر من عملية thread للشرط if بداخل الدالة getInstance لأن ذلك سيؤدي إلى أن كل عملية thread سيكون عندها نسخة object مختلفة مما يخل بنمط ال singleton تماماً
4. Thread safe singleton :
هذه الطريقة تحل المشلكة السابقة في حالة أكثر من عملية thread بإستخدام الكلمة المحجوزة synchronized التي تسمح ل thread واحد فقط أن يقوم بتنفيذ الدالة في نفس الوقت, مثال

public class ThreadSafeSingletonExample {
	private static ThreadSafeSingletonExample instance ;

	private ThreadSafeSingletonExample(){}

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

هذه الطريقة ممتازة جداً ولكنها تؤدي لتقليل الأداء نسبة لإستخدام ال synchronized ويمكننا معالجة ذلك بجعل ال synchronized داخل الشرط كالاتي :

public static ThreadSafeSingletonExample getInstance(){
  if(instance == null){
    synchronized (ThreadSafeSingletonExample.class){
         if(instance == null){
             instance = new ThreadSafeSingletonExample();
          }
     }
   }
}

5. Bill Pugh singleton :

كل الطرق السابقة تظهر فيها المشاكل في حالة وجود عمليات threads كثيرة جداً تحاول مناداة الدالة getInstance في نفس اللحظة, المشاكل تكون في الذاكرة تحديداً , فجاءت هذه الطريقة بطريقة مختلفة لحل هذه المشكلة بإستخدام inner helper class مثال :

public class BillPughSingletonExample {
   
    private BillPughSingletonExample(){};

    private static class SingletonHelper {
       private static final BillPughSingletonExample INSTANCE = new BillPughSingletonExample();
    }
 public static BillPughSingletonExample getInstance(){
   return SingletonHelper.INSTANCE;
 }
}

مع ملاحظة أن الكلاس الداخلي SingletonHelper لايتم تحميله في ال JVM بواسطة ال class loader عند تحميل الكلاس BillPughSingletonExample بل يتم ذلك عند مناداة الدالة وهذه الطريقة هي أفضل طريقة لتطبيق مفهوم ال singleton لما فيها من حلول لأغلب المشاكل السابقة .
وعموماً يمكنك تطبيق ال singleton بطرق أخرى كثيرة لكن المهم أن لا يكون هناك أكثر من instance للكلاس
أين تم إستخدامه في مكتبة جافا الأساسية : ال singleton مستخدم في كثير من مكتبات الجافا وأقرب مثال له في ال java.lang.Runtime
وسأواصل في بقية الأنماط إن شاء الله تعالى.