نمط Observer
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 68 من 87.
يُعد Observer Pattern نمط تصميم سلوكي يحدد علاقة واحد إلى متعدد بين الكائنات. فعندما تتغير حالة كائن واحد (الـ subject)، يتم إخطار جميع التابعين له (الـ observers) وتحديثهم تلقائياً. فكر في الأمر كأنه اشتراك في نشرة إخبارية؛ حيث يتلقى المشتركون تحديثات كلما نُشر محتوى جديد.
يتضمن النمط مكونين رئيسيين: موضوع (subject) يحتفظ بقائمة من المراقبين (observers) ويخطرهم بالتغييرات، ومراقبون يحددون طريقة تحديث (update method) للاستجابة للإشعارات:
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 Pattern) الاقتران الضعيف—فالموضوع (subject) لا يحتاج إلى معرفة الفئات الملموسة (concrete classes) لمراقبيه، بل يحتاج فقط إلى معرفة أنها تنفذ واجهة Observer. وهذا يجعل من السهل إضافة أنواع مراقبين جديدة دون تعديل الموضوع.
التحدي
سهللنقم ببناء نظام تنبيه لأسعار الأسهم باستخدام نمط المراقب (Observer Pattern)! ستقوم بإنشاء نظام حيث يقوم مؤشر الأسهم بإخطار العديد من المستثمرين كلما تغير سعر السهم، مما يسمح لكل مستثمر بالتفاعل مع تحديثات الأسعار بطريقته الخاصة.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
Observer.java: قم بتعريف واجهةObserverالتي سينفذها جميع المستثمرين. يجب أن تعلن عن طريقة واحدةupdate(String stockName, double price)يستخدمها المراقبون لتلقي الإخطارات حول تغيرات الأسعار.StockTicker.java: قم بإنشاء فئة الموضوع (subject class) التي تحتفظ بقائمة من المراقبين وتخطرهم بتغيرات الأسعار. يجب أن يحتويStockTickerالخاص بك على:حقل String خاص لاسم السهم (يتم تعيينه عبر المنشئ).
حقل double خاص للسعر الحالي (مبدئيًا 0).
قائمة خاصة لتخزين المراقبين المشتركين.
طريقة
subscribe(Observer observer)تضيف مراقبًا إلى القائمة.طريقة
unsubscribe(Observer observer)تزيل مراقبًا من القائمة.طريقة
setPrice(double price)تقوم بتحديث السعر ثم تخطر جميع المراقبين عن طريق استدعاء طريقةupdateالخاصة بهم مع اسم السهم والسعر الجديد.Investors.java: قم بإنشاء نوعين مختلفين من المستثمرين الذين ينفذون واجهةObserver:DayTrader- لديه حقل اسم خاص يتم تعيينه عبر المنشئ. عند التحديث، يطبع[name] received alert: [stockName] is now $[price] - Making quick trade!LongTermInvestor- لديه حقل اسم خاص يتم تعيينه عبر المنشئ. عند التحديث، يطبع[name] received alert: [stockName] is now $[price] - Holding steady.قم بتنسيق السعر بمنزلتين عشريتين.
Main.java: اجمع نظام المراقب الخاص بك معًا! ستتلقى ثلاثة مدخلات: اسم السهم (String)، وتحديث السعر الأول (double)، وتحديث السعر الثاني (double).قم بإنشاء
StockTickerباسم السهم المعطى. قم بإنشاءDayTraderباسم"Alice"وLongTermInvestorباسم"Bob"، وقم بإشراك كليهما في المؤشر.قم بتعيين السعر إلى قيمة الإدخال الأولى (يتم إخطار جميع المراقبين). ثم قم بإلغاء اشتراك Alice. أخيرًا، قم بتعيين السعر إلى قيمة الإدخال الثانية (يجب إخطار Bob فقط هذه المرة).
ستتلقى ثلاثة مدخلات بالترتيب: اسم السهم (String)، السعر الأول (double)، السعر الثاني (double).
يوضح هذا التحدي قوة نمط المراقب (Observer Pattern): لا يعرف StockTicker أي شيء عن أنواع المستثمرين المحددة — فهو يعرف فقط أنهم ينفذون Observer. عندما تقوم بإلغاء اشتراك Alice، فإنها تتوقف عن تلقي التحديثات بينما يستمر إخطار Bob. هذا الاقتران الضعيف (loose coupling) يجعل من السهل إضافة أنواع مستثمرين جديدة دون تغيير المؤشر!
ورقة مرجعية
نمط المراقب (Observer Pattern) هو نمط تصميم سلوكي ينشئ علاقة واحد إلى متعدد بين الكائنات. عندما تتغير حالة كائن واحد (الـ subject)، يتم إخطار جميع التابعين له (الـ observers) وتحديثهم تلقائيًا.
يتضمن النمط مكونين رئيسيين:
- Subject: يحتفظ بقائمة من المراقبين ويخطرهم بالتغييرات
- Observers: يحددون طريقة تحديث (update method) للاستجابة للإخطارات
الهيكل الأساسي:
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class Subject {
private List<Observer> observers = new ArrayList<>();
public void subscribe(Observer observer) {
observers.add(observer);
}
public void unsubscribe(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
class ConcreteObserver implements Observer {
public void update(String message) {
// التفاعل مع الإخطار
}
}مثال على الاستخدام:
Subject subject = new Subject();
subject.subscribe(new ConcreteObserver());
subject.notifyObservers("State changed!");
// يتلقى جميع المراقبين المشتركين التحديثيعزز نمط المراقب الارتباط الضعيف (loose coupling)—لا يحتاج الـ subject إلى معرفة الفئات الملموسة (concrete classes) لمراقبيه، بل يحتاج فقط إلى معرفة أنها تنفذ واجهة الـ Observer. وهذا يجعل من السهل إضافة أنواع مراقبين جديدة دون تعديل الـ subject.
جرّب بنفسك
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();
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
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أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر