نمط المراقب (Events)
جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 54 من 70.
يُنشئ نمط المراقب (Observer pattern) علاقة واحد إلى متعدد حيث يقوم كائن واحد (الموضوع) بإخطار عدة كائنات تابعة (المراقبين) عندما تتغير حالته. في لغة C#، توفر الـ events طريقة مدمجة لتنفيذ هذا النمط بأناقة.
يتضمن هذا النمط ناشرًا يقوم بإصدار الأحداث ومشتركين يستجيبون لها. إليك مثال عملي لمراقب أسعار الأسهم:
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يعزز نمط المراقب (Observer pattern) الاقتران الضعيف - لا تحتاج فئة Stock إلى معرفة أي شيء عن مراقبيها. يمكن للمراقبين الجدد الاشتراك دون تعديل الناشر، مما يجعل النظام سهل التوسيع والصيانة.
التحدي
سهللنقم ببناء نظام لمراقبة درجة الحرارة باستخدام نمط المراقب (Observer pattern) مع الأحداث (events). ستقوم بإنشاء مستشعر يقوم بإخطار مراقبين متعددين كلما تغيرت درجة الحرارة - وهو أمر مثالي لسيناريوهات مثل أنظمة التحكم في المناخ حيث تحتاج المكونات المختلفة إلى التفاعل مع تحديثات درجة الحرارة.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
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يوضح هذا القوة الأساسية لنمط المراقب - فالمستشعر لا يعرف أو يهتم بمن يستمع إليه. يمكن للمراقبين الاشتراك وإلغاء الاشتراك ديناميكياً، ويقوم الناشر ببساطة بإخطار كل من يستمع حالياً!
ورقة مرجعية
يُنشئ نمط المراقب (Observer pattern) علاقة واحد إلى متعدد حيث يقوم كائن واحد (الموضوع) بإخطار كائنات تابعة متعددة (المراقبين) عندما تتغير حالته.
في لغة C#، توفر الأحداث (events) طريقة مدمجة لتنفيذ هذا النمط. يتضمن النمط ناشراً (publisher) يقوم بإرسال الأحداث ومشتركين (subscribers) يستجيبون لها.
التنفيذ الأساسي للأحداث
قم بتعريف حدث باستخدام event Action<T> واستدعائه عند تغير الحالة:
public class Stock
{
private decimal _price;
public event Action<decimal> PriceChanged;
public decimal Price
{
get => _price;
set
{
_price = value;
PriceChanged?.Invoke(_price); // Notify all observers
}
}
}إنشاء المراقبين
المراقبون هم فئات (classes) تحتوي على دوال تتطابق مع توقيع الحدث:
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; // Subscribe
stock.PriceChanged += logger.OnPriceChanged; // Subscribe
stock.Price = 150.50m; // Both observers notified
stock.PriceChanged -= logger.OnPriceChanged; // Unsubscribe
stock.Price = 155.00m; // Only alert notifiedيعزز نمط المراقب (Observer pattern) الاقتران الضعيف (loose coupling) - حيث لا يحتاج الناشر إلى معرفة أي شيء عن مراقبيه. يمكن للمراقبين الجدد الاشتراك دون تعديل الناشر.
جرّب بنفسك
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: AlarmSystem و DisplayUnit إنشاء مثيلات لـ
// TODO: TemperatureChanged تسجيل كلا المراقبين في حدث
// TODO: (يتم إخطار كلا المراقبين) temp1 تعيين درجة حرارة المستشعر إلى
// TODO: من الحدث AlarmSystem إلغاء اشتراك
// TODO: (يتم إخطار وحدة العرض فقط) temp2 تعيين درجة حرارة المستشعر إلى
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
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