نمط Observer
جزء من قسم Object Oriented Programming في رحلة Dart على Coddy — الدرس 92 من 110.
يُنشئ نمط المراقب (Observer pattern) علاقة واحد إلى متعدد بين الكائنات: فعندما تتغير حالة كائن واحد (الموضوع - subject)، يتم إخطار جميع التابعين له (المراقبين - observers) تلقائياً. يُعد هذا النمط مثالياً لسيناريوهات مثل تحديثات واجهة المستخدم، أو أنظمة الأحداث، أو أي حالة تحتاج فيها أجزاء متعددة من تطبيقك إلى التفاعل مع التغييرات.
يتضمن النمط دورين رئيسيين: Subject (الموضوع) الذي يحتفظ بقائمة من المراقبين ويقوم بإخطارهم بالتغييرات، و Observers (المراقبون) الذين يقومون بتسجيل أنفسهم والاستجابة للتحديثات:
abstract class Observer {
void update(String message);
}
class NewsAgency {
final List<Observer> _observers = [];
String _latestNews = '';
void subscribe(Observer observer) {
_observers.add(observer);
}
void unsubscribe(Observer observer) {
_observers.remove(observer);
}
void publishNews(String news) {
_latestNews = news;
for (var observer in _observers) {
observer.update(_latestNews);
}
}
}
class NewsChannel implements Observer {
final String name;
NewsChannel(this.name);
@override
void update(String message) {
print('$name received: $message');
}
}
void main() {
var agency = NewsAgency();
var cnn = NewsChannel('CNN');
var bbc = NewsChannel('BBC');
agency.subscribe(cnn);
agency.subscribe(bbc);
agency.publishNews('Breaking news!');
// CNN received: Breaking news!
// BBC received: Breaking news!
}عندما يتم استدعاء publishNews، تقوم الوكالة بالمرور عبر جميع المراقبين المشتركين وتستدعي طريقة update الخاصة بهم. يمكن للمراقبين الاشتراك أو إلغاء الاشتراك في أي وقت، مما يجعل النظام مرنًا وضعيف الارتباط - لا يحتاج الموضوع إلى معرفة الأنواع المحددة لمراقبيه.
التحدي
سهللنقم ببناء نظام تنبيه لأسعار الأسهم باستخدام نمط المراقب (Observer pattern)! ستقوم بإنشاء نظام حيث يقوم مؤشر الأسهم بإخطار مستثمرين متعددين كلما تغير السعر، مما يسمح لكل مستثمر بالتفاعل مع تحديثات السوق بطريقته الخاصة.
ستقوم بتنظيم الكود الخاص بك في ملفين:
stock_system.dart: أنشئ نظام المراقب الخاص بك هنا:- فئة مجردة (abstract class) باسم
Investorتحتوي على طريقةvoid onPriceChange(String stockName, double price) - فئة
DayTraderالتي تنفذInvestor. تحتوي على حقلnameوطريقةonPriceChangeالخاصة بها تطبع:[name] sees [stockName] at $[price] - Making quick trade! - فئة
LongTermInvestorالتي تنفذInvestor. تحتوي على حقلnameوطريقةonPriceChangeالخاصة بها تطبع:[name] notes [stockName] at $[price] - Holding steady. - فئة
StockTicker(الموضوع) تحتوي على:- حقل
String stockName - قائمة خاصة لتخزين المستثمرين المشتركين
- طريقة
subscribe(Investor investor)لإضافة مستثمر - طريقة
unsubscribe(Investor investor)لإزالة مستثمر - طريقة
updatePrice(double newPrice)تقوم بإخطار جميع المستثمرين المشتركين بالسعر الجديد
- حقل
- فئة مجردة (abstract class) باسم
main.dart: استورد نظام الأسهم الخاص بك واستعرض نمط المراقب قيد التشغيل:- أنشئ
StockTickerلسهم باسمDART - أنشئ
DayTraderباسمAliceوLongTermInvestorباسمBob - قم بإشراك كلا المستثمرين في المؤشر
- قم بتحديث السعر إلى
150.50(يجب إخطار كلا المستثمرين) - اطبع سطراً فارغاً
- قم بإلغاء اشتراك Alice من المؤشر
- قم بتحديث السعر إلى
155.75(يجب إخطار Bob فقط الآن)
- أنشئ
يوضح هذا كيف ينشئ نمط المراقب نظاماً مفكك الارتباط (loosely coupled) - لا يحتاج StockTicker إلى معرفة ما إذا كان يقوم بإخطار متداولين يوميين أو مستثمرين طويلي الأجل، فهو يستدعي فقط onPriceChange لكل من هو مشترك!
المخرجات المتوقعة:
Alice sees DART at $150.5 - Making quick trade!
Bob notes DART at $150.5 - Holding steady.
Bob notes DART at $155.75 - Holding steady.ورقة مرجعية
يُنشئ نمط المراقب (Observer pattern) علاقة واحد إلى متعدد حيث يقوم الموضوع (subject) بإخطار جميع مراقبيه عندما تتغير حالته.
يحتوي هذا النمط على دورين رئيسيين:
- الموضوع (Subject): يحتفظ بقائمة من المراقبين ويخطرهم بالتغييرات
- المراقب (Observer): يسجل لدى الموضوع ويستجيب للتحديثات
التنفيذ الأساسي:
abstract class Observer {
void update(String message);
}
class Subject {
final List<Observer> _observers = [];
void subscribe(Observer observer) {
_observers.add(observer);
}
void unsubscribe(Observer observer) {
_observers.remove(observer);
}
void notifyObservers(String data) {
for (var observer in _observers) {
observer.update(data);
}
}
}
class ConcreteObserver implements Observer {
final String name;
ConcreteObserver(this.name);
@override
void update(String message) {
print('$name received: $message');
}
}الفوائد الرئيسية:
- اقتران ضعيف (Loose coupling) بين الموضوع والمراقبين
- يمكن للمراقبين الاشتراك أو إلغاء الاشتراك ديناميكيًا
- لا يحتاج الموضوع إلى معرفة أنواع المراقبين المحددة
جرّب بنفسك
import 'stock_system.dart';
void main() {
// TODO: 'DART' لمخزون يسمى StockTicker قم بإنشاء
// TODO: باسم 'Alice' DayTrader قم بإنشاء
// TODO: باسم 'Bob' LongTermInvestor قم بإنشاء
// TODO: قم بإشراك كلا المستثمرين في مؤشر الأسعار
// TODO: قم بتحديث السعر إلى 150.50 (يجب إخطار كلا المستثمرين)
// TODO: قم بطباعة سطر فارغ
// TODO: من مؤشر الأسعار Alice قم بإلغاء اشتراك
// TODO: قم بتحديث السعر إلى 155.75 (يجب إخطار Bob فقط الآن)
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةالمكتبات والـ Importsمقدمة في الـ OOPالـ Classes مقابل الـ Objectsالكلمة المفتاحية thisالـ Methodsمتغيرات الـ Instanceأساسيات الـ Constructorمراجعة - آلة حاسبة بسيطة4أمان الـ Null
مقدمة في أمان الـ NullNullable مقابل Non-Nullableعاملا الـ ? و !الكلمة المفتاحية Late وأمان الـ Nullالعوامل المدركة للـ Nullأمان الـ Null في الـ Classesمراجعة - نظام ملف تعريف المستخدم7الفئات المجردة والواجهات
الفئات المجردةالدوال المجردةالواجهات في Dartالواجهات الضمنيةالتنفيذ مقابل التوسيعالواجهات المتعددةمراجعة - حاسبة الأشكال10المجموعات والأنماط العامة
نظرة عامة على List و Set و Mapالمجموعات آمنة النوعالفئات العامةالدوال العامةقيود الأنماط العامةIterable و Iteratorملخص - التخزين العام13مفاهيم OOP المتقدمة
التركيب مقابل الوراثة (Composition vs Inheritance)دوال التوسعة (Extension Methods)الأصناف القابلة للاستدعاء (Callable Classes)الأصناف المختومة (Sealed Classes - Dart 3)السجلات (Records - Dart 3)الأنماط والمطابقة (Patterns & Matching - 3.0)التعدادات مع الدوال (Enums with Methods)2المنشئات في Dart
المنشئ الافتراضيالمنشئات المسماةقوائم التهيئةالمنشئات الثابتةالمنشئات المصنعيةالمنشئات الموجهةملخص - بناء الأشكال5التغليف (Encapsulation)
الأعضاء العامة مقابل الخاصةاتفاقية البادئة _الخصوصية على مستوى المكتبةتعمق في Getters و Settersإخفاء المعلوماتمراجعة - سجلات الطلاب8Mixins
مقدمة إلى Mixinsإنشاء Mixinsاستخدام Mixins متعددةالكلمة المفتاحية on في MixinsMixin مقابل الوراثة (Inheritance)Mixin مقابل الواجهة (Interface)مراجعة - نظام الحيوانات (Animal System)3خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticحقول Final و Constمتغيرات Lateالـ Static Methods والحقولدوال الـ Getters والـ Settersمراجعة - مدير الحساب البنكي6الوراثة
أساسيات الوراثةالكلمة المفتاحية superإعادة تعريف الدوال (Method Overriding)الوسم @overrideالكلمة المفتاحية final Classالدوال البانية والوراثةمراجعة - هيكلية الموظفين9تعدد الأشكال
أساسيات تعدد الأشكالتعدد الأشكال عبر الـ Interfacesالتحقق من النوع أثناء التشغيلالمعاملات is و asالكلمة المفتاحية Covariantملخص - معالج الدفع12البرمجة الكائنية غير المتزامنة (Async OOP)
Futures و async/awaitأساسيات الـ Streamsمتحكمات الـ Streamsالـ Constructors غير المتزامنةالعمليات غير المتزامنة في دوال الكلاسمراجعة - جالب البيانات (Data Fetcher)15أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع