نمط Observer
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 68 من 87.
نمط Observer Pattern هو نمط تصميم سلوكي يُنشئ علاقة واحد إلى متعدد بين الكائنات. عندما يغيّر كائن واحد (وهو subject) حالته، يتم إخطار جميع الكائنات التابعة له (وهي observers) وتحديثها تلقائيًا. فكّر فيه مثل الاشتراك في نشرة إخبارية: يتلقى المشتركون التحديثات كلما تم نشر محتوى جديد.
يتضمن النمط مكوّنين رئيسيين: موضوعًا يحتفظ بقائمة من المراقبين ويُخطرهم بالتغييرات، ومراقبين يعرّفون أسلوب تحديث للاستجابة للإشعارات:
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class NewsPublisher {
private List<Observer> subscribers = new ArrayList<>();
public void subscribe(Observer observer) {
subscribers.add(observer);
}
public void unsubscribe(Observer observer) {
subscribers.remove(observer);
}
public void publish(String news) {
for (Observer subscriber : subscribers) {
subscriber.update(news);
}
}
}
class EmailSubscriber implements Observer {
private String email;
public EmailSubscriber(String email) {
this.email = email;
}
public void update(String message) {
System.out.println("Email to " + email + ": " + message);
}
}عندما ينشر الناشر الأخبار، يتم إخطار جميع المشتركين تلقائيًا:
NewsPublisher publisher = new NewsPublisher();
publisher.subscribe(new EmailSubscriber("alice@mail.com"));
publisher.subscribe(new EmailSubscriber("bob@mail.com"));
publisher.publish("Breaking news!");
// المخرجات:
// بريد إلكتروني إلى alice@mail.com: Breaking news!
// بريد إلكتروني إلى bob@mail.com: Breaking news!يشجّع نمط Observer على الاقتران غير المحكم: لا يحتاج الموضوع إلى معرفة الفئات الملموسة للمراقبين، بل يحتاج فقط إلى معرفة أنها تطبّق الواجهة Observer. وهذا يجعل من السهل إضافة أنواع جديدة من المراقبين دون تعديل الموضوع.
التحدي
سهللنَبْنِ نظامًا للتنبيه عند تغيّر أسعار الأسهم باستخدام نمط المراقب! ستنشئ نظامًا يُخطر فيه شريط أسعار الأسهم عدة مستثمرين كلما تغيّر سعر السهم، مما يتيح لكل مستثمر التفاعل مع تحديثات السعر بطريقته الخاصة.
ستُنظّم شيفرتك عبر أربعة ملفات:
Observer.java: عرّف الواجهةObserverالتي سيطبّقها جميع المستثمرين. ينبغي أن تُعلن عن method واحدة هيupdate(String stockName, double price)، وتستخدمها الكائنات المراقِبة لتلقّي الإشعارات حول تغيّرات السعر.StockTicker.java: أنشئ الفئة الموضوعية التي تحتفظ بقائمة من الكائنات المراقِبة وتُخطرها بتغيّرات السعر. يجب أن تحتويStockTickerعلى:حقل String خاص لاسم السهم (يُحدَّد عبر constructor).
حقل double خاص للسعر الحالي (قيمته الأولية 0).
قائمة خاصة لتخزين الكائنات المراقِبة المشتركة.
method باسم
subscribe(Observer observer)تضيف كائنًا مراقبًا إلى القائمة.method باسم
unsubscribe(Observer observer)تزيل كائنًا مراقبًا من القائمة.method باسم
setPrice(double price)تحدّث السعر ثم تُخطر جميع الكائنات المراقِبة باستدعاء method الخاصة بهاupdateمع اسم السهم والسعر الجديد.Investors.java: أنشئ نوعين مختلفين من المستثمرين يطبّقان الواجهةObserver:DayTrader- يحتوي على حقل اسم خاص يُحدَّد عبر constructor. عند تحديثه، يطبع[name] received alert: [stockName] is now $[price] - Making quick trade!LongTermInvestor- يحتوي على حقل اسم خاص يُحدَّد عبر constructor. عند تحديثه، يطبع[name] received alert: [stockName] is now $[price] - Holding steady.نسّق السعر باستخدام منزلتين عشريتين.
Main.java: اجمع نظام المراقبة الخاص بك! ستتلقى ثلاثة مُدخلات: اسم السهم (String)، وتحديث السعر الأول (double)، وتحديث السعر الثاني (double).أنشئ
StockTickerباستخدام اسم السهم المعطى. أنشئDayTraderباسم"Alice"وLongTermInvestorباسم"Bob"، واشترك بهما مع شريط الأسعار.اضبط السعر على قيمة المُدخل الأول (يتلقى جميع المراقبين إشعارًا). ثم ألغِ اشتراك Alice. وأخيرًا، اضبط السعر على قيمة المُدخل الثاني (ينبغي إخطار Bob فقط هذه المرة).
ستتلقى ثلاثة مُدخلات بالترتيب: اسم السهم (String)، والسعر الأول (double)، والسعر الثاني (double).
يوضّح هذا التحدي قوة نمط المراقب: لا يعرف StockTicker شيئًا عن أنواع المستثمرين المحددة. إنه يعرف فقط أنها تطبّق Observer. عندما تلغي اشتراك Alice، تتوقف عن تلقي التحديثات بينما يستمر Bob في تلقي الإشعارات. يجعل هذا الاقتران المرن من السهل إضافة أنواع جديدة من المستثمرين دون تغيير شريط الأسعار!
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// قراءة المدخلات
String stockName = scanner.nextLine();
double firstPrice = scanner.nextDouble();
double secondPrice = scanner.nextDouble();
// TODO: إنشاء StockTicker باسم السهم المعطى
// TODO: إنشاء DayTrader باسم "Alice"
// TODO: إنشاء LongTermInvestor باسم "Bob"
// TODO: إشراك كل من Alice و Bob في ticker
// TODO: تعيين السعر إلى firstPrice (يتم إشعار كلا الملاحظين)
// TODO: إلغاء اشتراك Alice
// TODO: تعيين السعر إلى secondPrice (يتم إشعار Bob فقط)
scanner.close();
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت