نمط المراقب (Events)
جزء من قسم البرمجة كائنية التوجه في رحلة C# على Coddy. الدرس 54 من 70.
ينشئ نمط المراقب علاقة واحد إلى متعدد، حيث يُخطِر كائن واحد (الموضوع) عدة كائنات تابعة (مراقبين) عند تغيّر حالته. في C#، توفّر الأحداث طريقة مدمجة لتطبيق هذا النمط بأناقة.
يتضمن هذا النمط ناشرًا يطلق أحداثًا ومشتركين يستجيبون لها. إليك مثالًا عمليًا على مراقبة أسعار الأسهم:
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // أبلغ جميع المراقبين
}
}
}
public class PriceAlert
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Alert: Price is now {newPrice}");
}
}
public class PriceLogger
{
public void OnPriceChanged(decimal newPrice)
{
Console.WriteLine($"Logged: {newPrice}");
}
}يربط المشتركون أساليبهم بالحدث باستخدام +=، ويمكنهم إلغاء ربطها باستخدام -=:
var stock = new Stock();
var alert = new PriceAlert();
var logger = new PriceLogger();
stock.PriceChanged += alert.OnPriceChanged;
stock.PriceChanged += logger.OnPriceChanged;
stock.Price = 150.50m;
// الإخراج:
// تنبيه: السعر الآن 150.50
// تم التسجيل: 150.50
stock.PriceChanged -= logger.OnPriceChanged; // إلغاء الاشتراك
stock.Price = 155.00m;
// الإخراج: تنبيه: السعر الآن 155.00يعزز نمط المراقب الاقترانَ الضعيف - فلا تحتاج الفئة Stock إلى معرفة أي شيء عن مراقبيها. يمكن للمراقبين الجدد الاشتراك دون تعديل الناشر، مما يجعل النظام سهل التوسيع والصيانة.
التحدي
سهللنبنِ نظامًا لمراقبة درجة الحرارة باستخدام نمط المراقب مع الأحداث. ستنشئ مستشعرًا يُخطر عدة مراقبين كلما تغيرت درجة الحرارة، وهذا مثالي لحالات مثل أنظمة التحكم بالمناخ، حيث تحتاج مكونات مختلفة إلى الاستجابة لتحديثات درجة الحرارة.
ستنظم الكود في ثلاثة ملفات:
TemperatureSensor.cs: أنشئ صنفTemperatureSensorفي مساحة الأسماءMonitoring. هذا هو الناشر لديك، أي الكائن الذي ستراقبه الكائنات الأخرى. ينبغي أن يحتوي المستشعر على:- حقل خاص لتخزين درجة الحرارة الحالية
- حدث يُسمى
TemperatureChangedويستخدمAction<double>لإخطار المشتركين بدرجة الحرارة الجديدة - خاصية
Temperatureتُطلق الحدث كلما عُيّنت درجة الحرارة على قيمة جديدة
Observers.cs: أنشئ صنفي مراقبَين في مساحة الأسماء نفسها، ليشتركا في تلقي إشعارات تغيّر درجة الحرارة:DisplayUnit- يحتوي على دالةOnTemperatureChanged(double temp)تطبعDisplay: {temp}CAlarmSystem- يحتوي على دالةOnTemperatureChanged(double temp)تطبعAlarm Check: {temp}C
Program.cs: اجمع كل شيء بإنشاء مستشعر وكلا المراقبَين. اجعل كلا المراقبَين يشتركان في حدث المستشعر، ثم حدّث درجة الحرارة. بعد ذلك، ألغِ اشتراك نظام الإنذار وحدّث درجة الحرارة مرة أخرى لتوضيح أن وحدة العرض وحدها تتلقى الإشعار.
ستتلقى قيمتَي إدخال:
- قراءة درجة الحرارة الأولى (مثل
22.5) - قراءة درجة الحرارة الثانية (مثل
28.0)
ينبغي أن ينفذ برنامجك ما يلي:
- إنشاء المستشعر وكلا المراقبَين
- جعل كلا المراقبَين يشتركان في حدث
TemperatureChanged - تعيين درجة حرارة المستشعر إلى قيمة الإدخال الأولى (ينبغي إخطار كلا المراقبَين)
- إلغاء اشتراك
AlarmSystemفي الحدث - تعيين درجة حرارة المستشعر إلى قيمة الإدخال الثانية (ينبغي إخطار وحدة العرض وحدها)
على سبيل المثال، إذا كانت قيمتا الإدخال 22.5 و28.0، فينبغي أن يكون الناتج:
Display: 22.5C
Alarm Check: 22.5C
Display: 28Cيوضح هذا القوة الأساسية لنمط المراقب: فالمستشعر لا يعرف ولا يهتم بمن يستمع إليه. يمكن للمراقبين الاشتراك وإلغاء الاشتراك ديناميكيًا، ولا يفعل الناشر سوى إخطار من يستمع إليه حاليًا!
جرّب بنفسك
using System;
using Monitoring;
class Program
{
public static void Main(string[] args)
{
// اقرأ درجات الحرارة المُدخلة
double temp1 = Convert.ToDouble(Console.ReadLine());
double temp2 = Convert.ToDouble(Console.ReadLine());
// TODO: أنشئ نسخة من TemperatureSensor
// TODO: أنشئ نسختين من DisplayUnit وAlarmSystem
// TODO: اشترك بكلا المراقبين في الحدث TemperatureChanged
// TODO: اضبط درجة حرارة المستشعر على temp1 (يُخطَر كلا المراقبين)
// TODO: ألغِ اشتراك AlarmSystem في الحدث
// TODO: اضبط درجة حرارة المستشعر على temp2 (تُخطَر وحدة العرض فقط)
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات OOP
الملفات الخارجيةNamespaces والتوجيهاتمقدمة في Classes و Objectsالكلمة المفتاحية 'this'Methods والمعاملاتFields مقابل PropertiesConstructorsObject Initializersمراجعة - آلة حاسبة بسيطة4الوراثة
صيغة الوراثة الأساسية (:)الكلمة المفتاحية 'base'الكلمات المفتاحية Virtual و Overrideالأصناف المختومة (Sealed Classes)الفئة الأساسية 'object'مراجعة - هيكلية الموظفين7ميزات متقدمة
تحميل العوامل (Operator Overloading)المفهرسات (Indexers (this[]))إعادة تعريف ()ToStringدوال التوسعة (Extension Methods)ملخص - Custom List10أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singleton الآمن للخيوط (Thread-Safe)نمط المصنع (Factory Pattern)نمط المراقب (Events)نمط الاستراتيجية (Strategy Pattern)2الخصائص والأعضاء الساكنة (Static Members)
الخصائص المطبقة تلقائيًا (Auto-Implemented Properties)خصائص القراءة فقط والكتابة فقطالحقول والدوال الساكنة (Static)الفئات الساكنة (Static Classes)الأعضاء ذات البنية التعبيرية (Expression-Bodied Members)5تعدد الأشكال والواجهات (Polymorphism & Interfaces)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالواجهة مقابل الفئة المجردة (Abstract Class)الواجهات المتعددةالواجهات الصريحةUpcasting و Downcastingملخص - حاسبة الأشكال8مفاهيم البرمجة كائنية التوجه (OOP) المتقدمة
التركيب بدلاً من الوراثةالـ Generics (الأصناف والدوال)الـ Delegates والأحداثالـ Attributes والـ Reflectionواجهة IDisposable وجملة usingأساسيات Dependency Injectionتدرّب بنفسك: مترجم C# عبر الإنترنت