عندما تعمل على حاسوبك الشخصي، أو حتى على هاتفك الذكيّ، أو ربّما ساعتك الذكيّة. فأنت تستخدم الأحداث آلاف المرّات. الحدث هو وسيلة جميلة للتعبير عن أمر طارئ حدث لكائن برمجيّ. قد يكون هذا الأمر الطارئ عبارة عن نقرة زر فأرة، أو عن لمسة على شاشة جهازك الذكي. أو ضغطة مفتاح من لوحة المفاتيح الخاصّة بي وأنا أكتب هذا المقال، أو أن يكون أمرًا طارئًا يُعبّر عن حالة داخليّة ضمن نظام التشغيل. باختصار، هناك عدد كبير جدًّا من المصادر المختلفة أو المحتملة للأحداث.
العلاقة بين الأحداث Events والنوّاب Delegates
تعتمد الأحداث بشكل كليّ على النوّاب. وفي الحقيقة هي وسيلة لتهذيبها! لنستعير الصنف Car من البرنامج Lesson13_02 من الدرس السابق:
1 public class Car 2 { 3 public delegate void SpeedNotificatoinDelegate(string message); 4 5 private SpeedNotificatoinDelegate speedNotificationHandler; 6 7 public void RegisterWithSpeedNotification(SpeedNotificatoinDelegate handler) 8 { 9 this.speedNotificationHandler = handler; 10 } 11 12 public int CurrentSpeed { get; set; } 13 public int MaxSpeed { get; set; } 14 15 public Car() 16 { 17 CurrentSpeed = 0; 18 MaxSpeed = 100; 19 } 20 21 public Car(int maxSpeed, int currentSpeed) 22 { 23 CurrentSpeed = currentSpeed; 24 MaxSpeed = maxSpeed; 25 } 26 27 public void Accelerate(int delta) 28 { 29 CurrentSpeed += delta; 30 31 if (CurrentSpeed > MaxSpeed) 32 { 33 if(this.speedNotificationHandler != null) 34 { 35 string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", 36 CurrentSpeed, MaxSpeed); 37 38 speedNotificationHandler(msg); 39 } 40 } 41 } 42 }
كنت قد طلبت منك في التمرين الداعم رقم 2 من الدرس السابق أن تجري تعديلًا على الصنف Car بحيث تستغني عن التابع RegisterWithSpeedNotification، ولمّحت بأن تجعل الحقل speedNotificationHandler ذي محدّد وصول public. بعد إجراء هذا التعديل سيصبح الصنف Car على الشكل التالي:
public class Car { public delegate void SpeedNotificatoinDelegate(string message); public SpeedNotificatoinDelegate speedNotificationHandler; public int CurrentSpeed { get; set; } public int MaxSpeed { get; set; } public Car() { CurrentSpeed = 0; MaxSpeed = 100; } public Car(int maxSpeed, int currentSpeed) { CurrentSpeed = currentSpeed; MaxSpeed = maxSpeed; } public void Accelerate(int delta) { CurrentSpeed += delta; if (CurrentSpeed > MaxSpeed) { if (this.speedNotificationHandler != null) { string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", CurrentSpeed, MaxSpeed); speedNotificationHandler(msg); } } } }
سيعمل هذا الصنف بشكل ممتاز، حيث من الممكن أن نُسند نائبًا للحقل speedNotificationHandler من خارج الصنف Car بالشكل التالي:
Car car = new Car(100, 0); car.speedNotificationHandler = new Car.SpeedNotificatoinDelegate(OnExceedMaxSpeedHandler);
المشكلة هنا أنّ الحقل speedNotificationHandler أصبح مكشوفًا تمامًا، حتى أنّه من الممكن استدعاء التابع الذي يغلّفه هذا النائب، من خارج الصنف Car، وهذا يعدّ خرقًا لمبدأ مهم في البرمجة كائنيّة التوجّه ألا وهو التغليف Encapsulation. لا ينبغي أن يتمكّن أيّ أحدٍ من استدعاء التابع الذي يغلّفه الحقل السابق إلّا من داخل الصنف Car حصرًا، لأنّ النائب المُسند لذلك الحقل يُعبّر عن حالة داخليّة ضمن الصنف Car وهي تجاوز السرعة القصوى.
توفّر لنا سي شارب حلًا عمليًّا وأنيقًا لهذه المشكلة تتمثّل في استخدام الأحداث. بالنسبة للصنف Car السابق (بعد التعديل) يكفيك أن تَسِم الحقل speedNotificationHandler بالكلمة المحجوزة event ليتحوّل إلى حدث لا يمكن استدعاؤه إلّا من داخل الصنف Car. لكن سيكون هناك اختلاف صغير في طريقة إسناد النوّاب إلى هذا الحقل. يحتوي البرنامج Lesson14_01 على النسخة الجديدة للصنف Car مع تعديل بسيط ضمن التابع Main لندعم استخدام الأحداث:
1 using System; 2 3 namespace Lesson14_01 4 { 5 public class Car 6 { 7 public delegate void SpeedNotificatoinDelegate(string message); 8 9 public event SpeedNotificatoinDelegate speedNotificationHandler; 10 11 public int CurrentSpeed { get; set; } 12 public int MaxSpeed { get; set; } 13 14 public Car() 15 { 16 CurrentSpeed = 0; 17 MaxSpeed = 100; 18 } 19 20 public Car(int maxSpeed, int currentSpeed) 21 { 22 CurrentSpeed = currentSpeed; 23 MaxSpeed = maxSpeed; 24 } 25 26 public void Accelerate(int delta) 27 { 28 CurrentSpeed += delta; 29 30 if (CurrentSpeed > MaxSpeed) 31 { 32 if (this.speedNotificationHandler != null) 33 { 34 string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", 35 CurrentSpeed, MaxSpeed); 36 37 speedNotificationHandler(msg); 38 } 39 } 40 } 41 } 42 43 class Program 44 { 45 static void Main(string[] args) 46 { 47 Car car = new Car(100, 0); 48 49 car.speedNotificationHandler += new Car.SpeedNotificatoinDelegate(OnExceedMaxSpeedHandler); 50 51 for (int i = 0; i < 5; i++) 52 { 53 Console.WriteLine("Increasing speed by 30"); 54 car.Accelerate(30); 55 } 56 } 57 58 static void OnExceedMaxSpeedHandler(string message) 59 { 60 Console.WriteLine(message); 61 } 62 } 63 }
انظر كيف وضعنا الكلمة المحجوزة event بعد كلمة public في التصريح عن الحقل speedNotificationHandler في السطر 9. كما أرجو أن تلاحظ أيضًا التعديل الذي طرأ في السطر 49 على كيفيّة إسناد النائب الجديد إلى الحقل speedNotificationHandler:
car.speedNotificationHandler += new Car.SpeedNotificatoinDelegate(OnExceedMaxSpeedHandler);
لاحظ كيف استخدمنا العامل (=+) بدلًا من العامل (=). في الحقيقة سيؤدي استخدام العامل (=) في هذه الحالة إلى حدوث خطأ أثناء ترجمة البرنامج.
ولكن ماهي الفائدة من العامل (=+)؟ لهذا العمل فائدة كبيرة، فمن خلاله يمكن تسجيل عدّة نوّاب (وبالتالي عدّة توابع) ضمن الحدث speedNotificationHandler بنفس الوقت. مما يعني أنّ عبارة مثل تلك الموجودة في السطر 37:
speedNotificationHandler(msg);
ستؤدّي إلى استدعاء أي نائب (وبالتالي أي تابع) مسجّل في الحدث speedNotificationHandler بشكل متسلسل يراعي الترتيب الذي سُجّلت ضمنه هذه النوّاب ضمن الحدث speedNotificationHandler باستخدام العامل =+. نسمّي التابع OnExceedMaxSpeedHandler اصطلاحًا بمعالج الحدث.
الآن سنذهب أبعد من ذلك ونجري تغييرًا على عبارة التسجيل في الحدث في السطر 49 لتصبح على الشكل التالي:
car.speedNotificationHandler += OnExceedMaxSpeedHandler;
لاحظ هنا أنّنا قد أزلنا التعبير الذي ينشئ كائن من النائب SpeedNotificatoinDelegate، ووضعنا بدلًا من ذلك اسم التابع OnExceedMaxSpeedHandler مباشرةً بعد العامل (=+). في الحقيقة إنّ مترجم سي شارب ذكيّ كفاية ليعرف أنّه ينبغي عليه أن يُنشئ كائنًا جديدًا من النائب SpeedNotificatoinDelegate بشكل تلقائيّ يغلّف التابع OnExceedMaxSpeedHandler. إذا نفّذت البرنامج ستحصل على نفس الخرج المتوقّع.
ملاحظة: تتمتّع النوّاب أيضًا بميّزة التسجيل المتعدّد باستخدام العامل =+. ولكن هذه الميّزة تُستخدم مع الأحداث بشكل أكبر.
التوابع مجهولة الاسم Anonymous Methods
التوابع مجهولة الاسم هي من المزايا التي تسمح باختصار الشيفرة إلى حدٍّ كبير. فمن اسمها، يظهر أنَّه لا يوجد لمثل هذه التوابع اسم، وإنّما جسم فقط يحوي الشيفرة المطلوب تنفيذها. كمثال بسيط على التوابع مجهولة الاسم سنجري تعديلًا على البرنامج Lesson14_01 السابق ليستخدم تابعًا عديم الاسم بدلًا من التابع OnExceedMaxSpeedHandler. سيكون هذا التعديل في السطر 49 ليصبح على الشكل التالي:
car.speedNotificationHandler += delegate(string message) { Console.WriteLine(message); };
لاحظ هنا أنّنا قد استخدمنا الكلمة المحجوزة delegate بعد العامل =+ بعد ذلك الوسائط التي يقبلها هذا التابع ثم جسم التابع المحاط بالحاضنة. الشيء الوحيد الناقص هو اسم التابع. من الواضح أنّ تعريف هذا التابع عديم الاسم يجب أن يتطابق مع تعريف النائب الذي صُرّح الحدث speedNotificationHandler بناءً عليه. الآن يمكن التخلّص من التابع OnExceedMaxSpeedHandler. انظر إلى البرنامج Lesson14_02 الكامل:
1 using System; 2 3 namespace Lesson14_02 4 { 5 public class Car 6 { 7 public delegate void SpeedNotificatoinDelegate(string message); 8 9 public event SpeedNotificatoinDelegate speedNotificationHandler; 10 11 public int CurrentSpeed { get; set; } 12 public int MaxSpeed { get; set; } 13 14 public Car() 15 { 16 CurrentSpeed = 0; 17 MaxSpeed = 100; 18 } 19 20 public Car(int maxSpeed, int currentSpeed) 21 { 22 CurrentSpeed = currentSpeed; 23 MaxSpeed = maxSpeed; 24 } 25 26 public void Accelerate(int delta) 27 { 28 CurrentSpeed += delta; 29 30 if (CurrentSpeed > MaxSpeed) 31 { 32 if (this.speedNotificationHandler != null) 33 { 34 string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", 35 CurrentSpeed, MaxSpeed); 36 37 speedNotificationHandler(msg); 38 } 39 } 40 } 41 } 42 43 class Program 44 { 45 static void Main(string[] args) 46 { 47 Car car = new Car(100, 0); 48 49 car.speedNotificationHandler += delegate(string message) 50 { 51 Console.WriteLine(message); 52 }; 53 54 for (int i = 0; i < 5; i++) 55 { 56 Console.WriteLine("Increasing speed by 30"); 57 car.Accelerate(30); 58 } 59 } 60 } 61 }
ملاحظة: يوجد نائب جاهز موجود ضمن مكتبة FCL اسمه EventHandler وظيفته توفير الدعم للأحداث الجديدة التي نعرّفها، بحيث لا نضطّر إلى التصريح عن نائب جديد في كلّ مرّة نريد فيها التصريح عن حدث جديد. يغلّف النائب EventHandler أي تابع يتطلّب وسيطين الأوّل من النوع object والذي يمثّل الكائن الذي أصدر الحدث، والثاني من النوع EventArgs وهو كائن يحتوي على بعض المعلومات الإضافيّة عن الحدث.
تمارين داعمة
تمرين 1
ليكن لدينا الصنف التالي:
class Counter { private int currentValue = 0; public void Increase() { currentValue++; } public void Decrease() { currentValue--; } }
أجرِ تعديلًا على هذا الصنف بحيث تصرّح عن الحدث Notification الذي يُفعَّل عندما تصبح قيمة الحقل currentValue من مضاعفات العدد 5 فقط.
(تلميح: الأعداد السالبة ليست من مضاعفات 5. والعدد 5 هو مضاعف لنفسه).
تمرين 2
أجرِ تعديلًا على البرنامج Lesson14_02 السابق لتستغني عن النائب SpeedNotificatoinDelegate تمامًا، بحيث تستخدم النائب الجاهز EventHandler عوضًا عنه.
(تلميح: سيتطلّب الأمر تعديل الوسائط الممرّرة إلى معالج الحدث لتتطابق مع الوسائط التي يحتاجها النائب EventHandler)
الخلاصة
تعرّفنا في هذا الدرس على الأحداث Events. تلك التقنيّة المهمّة التي تعتمد عليها تطبيقات سطح المكتب desktop applications بشكل أساسيّ، فضلًا عن باقي أنواع التطبيقات مثل تطبيقات الويب، وتطبيقات الأجهزة المحمولة، وأي نوع من أنواع التطبيقات التي تتطلّب التفاعل الداخلي مع نظام التشغيل أو الخارجيّ مع المستخدم.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.