كيف تبني نظام أحداث خاص بحزمتك إطلالة على الأحداث في حزمة المحرك

السلام عليكم

كيف حالكم يا شباب
كالعادة لست أتوقع أن يقرأ المقال الكثير
لكني كنت وعدت الأخ عصام في منتديات الفريق العربي
حين سألني عن نظام الأحداث events في حزمة المحرك AAnimation
بأني سأكتب شرحاً لآلية العمل
طبعاً أطلت جداً في الإيفاء بوعدي
وهذه ميزة لدي لا أحدد موعد انتهاء العمل
فيصبح وعدي بالإنتهاء منه مجرد سراب
لذا أتوقع حين أتم هذا المقال أن يكون عصام فهم الموضوع من تلقاء نفسه
وأن أكون أنا ضيعت وقتي في كتابة مقال لن يقرؤه حتى عصام :)
على كل هناك احتمال بسيط أن يكون عصام نسي الموضوع
-وأنا فعلاً صادق حين أقول احتمال بسيط لأن عصام لا يستهان به على الإطلاق-

ما الفائدة من الأحداث events؟
معروف أن الحزم packges وجدت لكي تسهل على المبرمجين العمليات المعقدة
أما لماذا وجدت الأحداث events فالأحداث وجدت لكي تعطي المجال لمستخدمي الحزم
عند حصول تغيير في حالة الكائنات داخل الحزمة من معالجة هذا التغيير بأسلوب يختلف من مبرمج إلى آخر
لنأخذ مثالاً على هذا عندما يضغط (بضم الياء) على زر في برنامج أ
فإن البرنامج يقوم بطباعة رسالة يوم جميل لا تنسى أن تسبح الله
أما في برنامج ب فعندما تضغط الزر فإنه سيشغل لك مقطعاً صوتياً

أي نظام أحداث يحتوي على جزئين أساسيين
1- المصدر للحدث Event Source
وهو الكائن أو الإنسان المسبب للحدث

2- المستمع للحدث Listner
وهو الكائن المسؤول عن معالجة الحدث
نلاحظ أن المستع دائماً كائن
لكن المصدر قد يكون كائن وقد يكون إنسان يتفاعل مع البرنامج

أعرف أن الكلام غير واضح لكن تعالوا نضرب مثالاً على الموضوع
عندما تضغط على زر رسالة ترحيبية في نافذة
فتحصل على رسالة “مرحباً بك في برنامج الأحداث”

تفاصيل الحدث في المثال السابق
الحدث:الضغط على الزر
مصدر الحدث:إنسان

المثال السابق كان مصدر الحدث فيه إنسان
نأخذ مثال آخر يكون فيه مصدر الحدث كائن

برنامج مؤقت Timer
بعد كل ساعة يصدر صوت يذكر المستخدم بأن ساعة انتهت

الحدث:مرور ساعة
مصدر الحدث:كائن من النوع مؤقت

أظن الأمور تحسنت بالنسبة للحدث ومصدر الحدث
لكني أظنها غامضة بعض الشيء بالنسبة للمستمع الحدث
هنا يبدأ الجد ويبدأ العمل
المستمع هو عبارة عن واجهة أو فئة مجردة
(يفضل أن تكون واجهة في جافا لأن جافا لا تدعم الوراثة المتعددة)
يتم تصميمها من قبل مبرمج الحزمة
لكن يتم بناؤها أو وراثتها من قبل المستخدم
ثم تسجيله ضمن قائمة الأحداث الخاصة بالحزمة
وستقوم الحزمة بتولي إخبار المستمع بالأحداث التي تحدث بها
أقصد بتصميمها كتابة قواعدها وتحديد آلية وكيفية الاستخدام
(من ناحية عملية كتابة رؤوس header الدوال التي يجب أن يقوم المستخدم للواجهة بإعادة كتابتها)
وأقصد ببناؤها أي لو كانت واجهة ووراثتها أي لو كانت فئة

يبدو أن الموضوع لازال يحتوي على الكثير من اللبس
وبما أن المقال يتضح بالمثال فنأخذ مثال
(تظن نفسك شاعر يا دب)
الموضوع يحتاج إلى تركيز يا أخ عبده
فاتركني وشأني لو سمحت
المثال هو ActionListner في حزمة swing في جافا
التصميم قام به فريق تطوير swing وهو كالتالي

package java.awt.event;

import java.util.EventListener;

public interface ActionListener extends EventListener {
    public void actionPerformed(ActionEvent e);

}

تلاحظون أن التصميم واجهة interface وأنا أدعم أن تكون هكذا دائماً
لأن الواجهات أكثر مرونة في التعامل من ناحية الوراثة
ولأني فعلياً لم أجد حالة تستدعي كون التصميم فئة مجردة AbstractClass
لكني أذكرها هنا لاحتمالية وجود ما يستدعي ذلك
بالنسبة لآلية بناء المستمع
هذا الجزء ليس مهماً لنا مطوري الحزمة
وإنما مهم للمبرمجين
على كل هذا مثال على بناء الواجهة ActionListener

package com.modonat-alaa;

import java.util.EventListener;

public Class Implementor implements ActionListenertListener {
    public void actionPerformed(ActionEvent e){
    	//doing something
    }

}

مازال الموضوع غير واضحاً
كيف يتم التعامل بين مصدر الحدث والمستمع للحدث
يأتي هنا دور تسجيل الحدث
بعد أن قمنا ببناء الواجهة الخاصة بالحدث
كل كائن نقوم ببناؤه من هذه الفئة يسمى مستمع
لكنه مستمع لا يستمع
(ما هذا الكلام الغريب تقول لنا مستمع ثم تقول لنا لا يستمع)
يا أخ عبده الفكرة كالتالي صحيح أنك قمت ببناء مستمع جديد
لكنك لم تحدد لمن يستمع هذا المستمع
لكي نجعل المستمع يستمع يجب أن نوفر في الحزمة دالة يتم تسجيل المستمع من خلالها
دوال تسجيل المستمع تنقسم إلى قسمين
1- دالة set
2- دالة add
إذا كنا نرى أن الحدث لا يصح أن يكون له أكثر من مستمع
نستخدم الدالة set
مثال:لا أجد مثالاً فعلياً لكني أتوقع وجود هكذا شيء
إذا كنا نرى أن الحدث يصح أن يكون له أكثر من مستمع
نستخدم الدالة add
مثال:الأمثلة لا تعد ولا تحصى أنا نفسي قمت بعمل نظامين بهذه الطريقة
(لا والله استفدنا منك ومن أمثلتك واحد لا تجد عليه مثال والآخر أمثلة لا تعد ولا تحصى)
خبرتي في الموضوع جيدة يا عبده لكنها لم تصل إلى الكمال
لذا أنا أحاول أن أعطيكم خلاصة تفكري في الموضوع
لكني لا أضمن لكم أن يكون تفكيري سليماً مئة في المئة

لكي يتضح الموضوع بشكل أكبر سنقوم بعمل حزمة بسيطة
الحزمة تمثل عدد صحيح
لكن عند حصول أي تعديل على العدد نريد أن تنتج بعض الأحداث
1- العدد الجديد أكبر من العدد القديم
2- العدد الجديد أقل من العدد القديم
دعونا في البداية نقوم بتصميم الواجهة الخاصة بالحدث

package integer.event;
import java.util.ArrayList;
import java.util.List;
/**
 * هذه الفئة تمثل عدد صحيح
 * لكنه يقوم بتوليد أحداث عند التغييرات التالية
 * تغيير العدد الحالي إلى عدد أكبر
 * تغيير العدد الحالي إلى عدد أصغر
 * @author علاء الصالحي
 *
 */

public class IntegerClass {
	/**
	 * هذا المتغير يمثل القيمة للعدد الصحيح
	 */
	private int value;
	/**
	 * هذا المتغير يحتوي على قائمة المستمعين
	 * الذين يجب تنبيههم عند حصول تعديلات على العدد
	 */
	private List<IntegerEvent> integerEvent;
	public IntegerClass(int value){
		this.value=value;
		integerEvent=new ArrayList<IntegerEvent>();
	}
	public int getValue (){
		return value;
	}
	/**
	 * هذه الدالة التي يتم من خلالها تغيير العدد الصحيح
	 * وهي الدالة التي تنتج منها الأحداث
	 * طبعاً نحن كمصممين للحزمة
	 * يجب علينا تنبيه كل المستمعين إلى حصول حدث جديد
	 * @param value القيمة التي سيتغير لها العدد
	 */
	public void setValue(int value) {
		if(value>this.value){
		//هنا نقوم بعملية الفحص للتأكد من كون القيمة أكبر من القيمة القديمة
			doGreaterEvent();//وهنا يتم تنبيه المستمعين للحدث
		}
		else if(value<this.value){
		//هنا نقوم بعملية الفحص للتأكد من كون القيمة أصغر من القيمة القديمة
			doLesserEvent();//وهنا يتم تنبيه المستمعين للحدث
		}
		this.value = value;
	}
	private void doLesserEvent() {
		for (int i = 0; i < integerEvent.size(); i++) {
			//نقوم بالمرور على كل المستمعين وتنفيذ الدالة المسؤولة عن الحدث
			integerEvent.get(i).newVlueLesserThanOldValue();
		}	
		
	}
	private void doGreaterEvent() {
		for (int i = 0; i < integerEvent.size(); i++) {
			integerEvent.get(i).newVlueGreaterThanOldValue();
		}		
	}
	/**
	 * هنا يتم إضافة مستمع جديد لهذا الكائن
	 * @param event
	 */
	public void addIntegerEvent(IntegerEvent event) {
		if(event!=null)
			integerEvent.add(event);
	}

}

الآن سنكتب مثال بسيط على كيفية استخدام الفئة IntegerClass

package test;

import integer.event.IntegerClass;

public class Test {
	public static void main(String[] args) {
		IntegerClass integerClass=new IntegerClass(10);
		integerClass.addIntegerEvent(new IntegerEventImpl());
		integerClass.setValue(12);
		integerClass.setValue(10);
	}

}

لاحظوا الآن الجملة المطبوعة في console مع أني لم أقم بطباعة أي شيء هنا
وعلى رأي الأخ أيمن أبو سمرة سحر :)
فعلياً الأمر لا يتعدى كون نظام الأحداث الذي قمنا به يعمل بشكل جيد

في الخاتمة:
لكي أكون صادق معكم الأحداث التي فيها تفاعل من الإنسان ليش من السهولة بمكان أن يتم التعامل معها في جافا
بل الأمر يحتاج إلى بعض التعقيدات التي قد تلجأك لـ jni
في رأيي يمكنك الاكتفاء بالأحداث التي توفرها جافا مبدئياً
أمر أخير قبل أن أنهي هذا المقال
لابد أن الأخوة أصحاب الخبرة في تصميم الأنماط Design pattern
لاحظوا أن الأحداث قريبة من نمط المراقب observer
ربما أتكلم عن نمط المراقب بتفصيل في مقال آخر
على كل المهم أن الفكرة وصلت
لا تنسوا أن تدفعوا الإشتراك الشهري الخاص بمدونتي
(احم احم عن أي اشتراك تتكلم أيها الوغد ثم على ماذا سندفع لك اشتراكاً على مقالتك الغبية أم على ثقل دمك)
احم احم كنت أمزح يا أخ عبده
(اها إذن لا تعيدها يا …)
ماشي ماشي يا أخ عبده على الأقل كف عن السباب القبيح
أختم قبل أن يقرر الأخ عبده أني أحتاج إلى علقة سخنة

تحياتي
المراجع:
لا شيء يمكن اعتباره كمرجع إلا لو اعتبرنا عملي في حزمة المحرك كمرجع
على العموم هذه وصلة لآخر إصدارة من الحزمة

Tags: , , , , ,

12 Responses to “كيف تبني نظام أحداث خاص بحزمتك إطلالة على الأحداث في حزمة المحرك”

  1. abuouf قال:

    أخي لما التشائم
    ان شاء الله هتلاقي ناس كتير هتقراه
    وان شاء الله اقرأه هو والموضوع السابق وارد عليك بس لما اخلص امتحانات نصف الترم
    :)

  2. admin قال:

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

    تحياتي

  3. Speed_Of_Light قال:

    مقالة جيدة … وظريفة !
    أنا قرأت المقالة ! ألا يكفي هذا ! :)
    عودة إلى الجد:
    ما الذي يميز نظام الأحداث ؟ أي متى يمكننا أن ندعو تحقيق لواجهة (interface implementation) حدثاً ؟! (أو بالأحرى مستمع لحدث)
    هل برأيك أن كون القيمة الجديدة في مثالك (أكبر أو أصغر من القيمة القديمة) هو حدث بحاجة إلى معالجة ؟
    الأمر نسبي لحد كبير، والشائع استخدام كلمة “حدث” مع الواجهات المرئية بشكل خاص.
    ولم أستغرب من “السحر” على أية حال، فلو جردت الكلمات من مضامينها لوجدته استدعاء طبيعي لتابع موجود في صف يدعى IntegerEventImpl يحقق الواجهة IntegerEvent.

    بالمناسبة … اسم الطريقة newVlueGreaterThanOldValue() بحاجة إلى تصحيح :)
    Vlue >> Value

  4. admin قال:

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

    لو ألتبس عليك الموضوع أخبرني وسأحاول إعادة الصياغة لكلامي

    تحياتي

  5. Speed_Of_Light قال:

    كان من الممكن إنشاء صف ابن من IntegerClass وإعادة كتابة override الطريقة setValue فيه.وننكتب فيها بكل بساطة:

    public class childIntegerClass extends IntegerClass {
    	public childIntegerClass(int value){
    		super(value);
    	}
    	public void setValue(int value) {
    		if(value>this.value){
    			// Your code goes here
    		} else{
    			// Your code goes here
    		}
    	}
    }

    ملاحظات:
    – هذا الكود ليس أطول من الصف IntegerEventImpl الذي يجب على مستخدم الحزمة كتابته على أيه حال! كما يوفر كل العذاب الذي قام به كاتب الحزمة.
    – في هذه الحال ستصبح الواجهة IntegerClass صفاً أباً، ويمكننا اختصار معظم الكود المضمن فيه.
    – بخصوص الطريقة setValue():
    1- أظن أن هذا خطأ بالتنسيق وهو يظهر بشكل خاطئ:

    else if(value < integerEvent.size(); i++) {
    integerEvent.get(i).newVlueLesserThanOldValue();
    }

    2- لا أدري لماذا قمت بتفصل الكود المكتوب في doGreaterEvent إلى طريقة خاصة ولم تفعل نفس الشيء في طريقة تسميها doLesserEvent !
    (أتمنى أن تقوم بإعادة تنسيق التعليق إن لم يظهر بالشكل الملائم)

  6. admin قال:

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

    أنا أتفق معاك أن المثال سهل هنا
    لكن هناك أمثل أصعب بكثير ولا يمكنك أن تقوم بالأمر بالبساطة التي قمت بها هنا
    بل لا يمكنك القيام به أبداً
    بالمناسبة صن لديها مجموعة أحداث كثيرة ليست عبارة عن واجهات
    أذكر منها الأحداث المتعلقة بـ Document في JTextPane

    بالنسبة للدالة setValue كان لدي مشكلة قمت بإصلاحها
    بعد تنبيهك لي

    أرحب بأي أسئلة أو استفسارات أخرى
    لكي تجد للموضوع معنى اطلع على حزم معقدة ستجد أن الموضوع ليس بهذه البساطة
    ولأعطيك مثال بسيط حزمة AAnimation على بساطتها
    صعب على الأخ عصام فهم الأحداث فيها

    تحياتي

  7. رباب ابراهيم قال:

    اجو منك كتبة موضوع عن Enter faces,lوماهى استخداماته وبرنامج يوضح الفكره العامة له خاصة وان ادرس oopبلغة الجافا

  8. admin قال:

    أخت رباب هل تقصدين interface؟
    لو كنت تقصدينه فأنا كتبت موضوع مسبق عنه
    الواجهات interfaces

    تحياتي

  9. mksoft قال:

    شكرا يا علاء على الموضوع ,,
    لكن اصارحك لم استوعب الموضوع بشكل جيد ..
    سأحاول قراءته مجددا .. بإذن الله تعالى.

  10. admin قال:

    العفو يا محمد
    خذ وقتك يا محمد
    وأخبرني ما النقاط التي تظن أنها تحتاج توضيح وبإذن الله سأقوم بالتوضيح

    تحياتي

  11. […] كيف تبني نظام أحداث خاص بحزمتك إطلالة على الأحداث في حز… […]

  12. ممكن سؤال انا عندي زر في برنامج معين
    ابغاه لما ينضغط يفتحلي موقع على الانترنت
    مثل زر مكتوب فيه تويتر لما احد يضغط عليه
    ينقله لتويتر الخ
    هذي كيف اسويها ما عرفت لها ابدا ابدا

Leave a Reply