السلام عليكم
انقل لكم هذه التدوينة من مدونتي
وأتمنى أن تحوز على رضاكم
كيف حالكم يا شباب
كالعادة لست أتوقع أن يقرأ المقال الكثير
لكني كنت وعدت الأخ عصام في منتديات الفريق العربي
حين سألني عن نظام الأحداث 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;
public interface IntegerEvent {
void newVlueGreaterThanOldValue();
void newVlueLesserThanOldValue();
}بعد ذلك نقوم بكتابة الفئة عدد صحيح بأحداث IntegerEvent
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);
}
}وبهكذا نكون أنهينا تصميم الحزمة الخاصة بنا مع وجود نظام أحداث فيها
(ما الذي أنهيناه يا أخ علاء لا أرى شيئاً يعمل)
بصراحة يا أخ عبده تصميم الحزم ليس له علاقة بأن ترى شيئاً يعمل
بل المفروض أن لا ترى شيئاً يعمل
الفكرة في الموضوع ككل أنك تساعد الآخرين في كتابة برامجهم
بأن تقوم بتسهيل الأمور عليهم
على كل لن أخيبك يا أخ عبده
سنرى معاً كيف يمكنني أن أستخدم الحزمة التي قمنا بتصميمها قبل قليل
في البداية علينا أن نقوم ببناء الواجهة IntegerEvent
وهذا تمثيل بسيط فقط سنقوم بطباعة كلمتي greater و lesser
package test;
import integer.event.IntegerEvent;
public class IntegerEventImpl implements IntegerEvent {
@Override
public void newVlueGreaterThanOldValue() {
System.out.println("greater");
}
@Override
public void newVlueLesserThanOldValue() {
System.out.println("lesser");
}
}الآن سنكتب مثال بسيط على كيفية استخدام الفئة 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
ربما أتكلم عن نمط المراقب بتفصيل في مقال آخر
على كل المهم أن الفكرة وصلت
لا تنسوا أن تدفعوا الإشتراك الشهري الخاص بمدونتي
(احم احم عن أي اشتراك تتكلم أيها الوغد ثم على ماذا سندفع لك اشتراكاً على مقالتك الغبية أم على ثقل دمك)
احم احم كنت أمزح يا أخ عبده
(اها إذن لا تعيدها يا ...)
ماشي ماشي يا أخ عبده على الأقل كف عن السباب القبيح
أختم قبل أن يقرر الأخ عبده أني أحتاج إلى علقة سخنة
تحياتي
المراجع:
لا شيء يمكن اعتباره كمرجع إلا لو اعتبرنا عملي في حزمة المحرك كمرجع
على العموم هذه وصلة لآخر إصدارة من الحزمة
