Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط Observer

جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 94 من 104.

يحدد نمط المراقب (Observer pattern) علاقة تبعية واحد إلى متعدد بين الكائنات: فعندما تتغير حالة كائن واحد (الموضوع)، يتم إخطار جميع التابعين له (المراقبين) تلقائيًا. يُعد هذا النمط مثاليًا لأنظمة الأحداث، أو تحديثات واجهة المستخدم، أو أي سيناريو تحتاج فيه كائنات متعددة إلى التفاعل مع التغييرات.

يتضمن النمط دورين أساسيين: Subject الذي يحتفظ بقائمة من المراقبين ويقوم بإخطارهم، و Observers الذين يقومون بتنفيذ واجهة تحديث:

#include <iostream>
#include <vector>
#include <algorithm>

class Observer {
public:
    virtual void update(int value) = 0;
    virtual ~Observer() = default;
};

class Subject {
    std::vector<Observer*> observers;
    int state = 0;
    
public:
    void attach(Observer* obs) {
        observers.push_back(obs);
    }
    
    void detach(Observer* obs) {
        observers.erase(
            std::remove(observers.begin(), observers.end(), obs),
            observers.end());
    }
    
    void setState(int value) {
        state = value;
        notify();
    }
    
    void notify() {
        for (Observer* obs : observers) {
            obs->update(state);
        }
    }
};

class Display : public Observer {
    std::string name;
public:
    Display(const std::string& n) : name(n) {}
    void update(int value) override {
        std::cout << name << " received: " << value << "\n";
    }
};

int main() {
    Subject sensor;
    Display screen1("Screen1"), screen2("Screen2");
    
    sensor.attach(&screen1);
    sensor.attach(&screen2);
    sensor.setState(42);  // تم إخطار كلتا الشاشتين
}

عندما يتم استدعاء setState()، يقوم الموضوع بالتكرار عبر جميع المراقبين المسجلين ويستدعي تابع update() الخاص بهم. يمكن للمراقبين الانضمام أو الانفصال في أي وقت، مما يجعل النظام مرنًا وضعيف الارتباط.

استخدم نمط Observer عندما تتطلب التغييرات في كائن واحد تحديث كائنات أخرى، ولا تريد أن تكون هذه الكائنات مرتبطة ببعضها البعض بشكل وثيق.

challenge icon

التحدي

سهل

لنقم ببناء نظام مراقبة محطة أرصاد جوية (Weather Station) باستخدام نمط المراقب (Observer pattern). ستقوم بإنشاء محطة أرصاد تتبع بيانات درجة الحرارة وتقوم تلقائياً بإخطار وحدات عرض متعددة كلما تغيرت درجة الحرارة — وهو تطبيق كلاسيكي من واقع الحياة لهذا النمط.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • Observer.h: تحديد واجهة المراقب (observer interface) ومراقب عرض ملموس.

    قم بإنشاء فئة مجردة Observer تحتوي على دالة افتراضية بحتة (pure virtual method) وهي update(double temperature) التي تنفذها الكائنات المراقبة لتلقي الإشعارات، بالإضافة إلى مدمر افتراضي (virtual destructor).

    ثم قم بإنشاء فئة TemperatureDisplay ترث من Observer. كل وحدة عرض لها اسم (string) يتم تعيينه من خلال المنشئ (constructor) الخاص بها. عندما يتم استدعاء update()، يجب أن تطبع:

    [DisplayName]: Temperature is [temperature] degrees
  • WeatherStation.h: إنشاء الموضوع (subject) الذي يحتفظ بدرجة الحرارة ويخطر المراقبين.

    يجب أن تقوم فئة WeatherStation الخاصة بك بـ:

    • تخزين قائمة من مؤشرات المراقبين (observer pointers) ودرجة الحرارة الحالية (قم بتهيئتها إلى 0.0)
    • امتلاك دالة attach(Observer* obs) لتسجيل المراقبين
    • امتلاك دالة detach(Observer* obs) لإزالة المراقبين
    • امتلاك دالة setTemperature(double temp) تقوم بتحديث درجة الحرارة وإخطار جميع المراقبين المتصلين
    • امتلاك دالة خاصة notify() تستدعي update() لكل مراقب مع درجة الحرارة الحالية
  • main.cpp: عرض نمط المراقب (Observer pattern) قيد التشغيل.

    اقرأ ثلاثة مدخلات:

    1. اسم وحدة العرض الأولى (string)
    2. اسم وحدة العرض الثانية (string)
    3. قيمة درجة الحرارة (double)

    قم بإنشاء WeatherStation وكائنين من نوع TemperatureDisplay بالأسماء المقدمة. قم بربط كلا العرضين بالمحطة، ثم اضبط درجة الحرارة على القيمة المدخلة. يجب أن يتلقى كلا العرضين التحديث تلقائياً ويطبعا رسائلهما.

    بعد ذلك، قم بفصل العرض الأول واضبط درجة حرارة جديدة تزيد بمقدار 5 درجات عن القيمة المدخلة. يجب أن يتلقى العرض الثاني فقط هذا التحديث.

على سبيل المثال، مع المدخلات Kitchen و Bedroom و 22.5:

Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degrees

مع المدخلات Office و Lobby و 18.0:

Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degrees

لاحظ كيف ينشئ نمط المراقب نظاماً مفككاً (loosely coupled) — لا تحتاج محطة الأرصاد الجوية إلى معرفة أي شيء عن وحدات العرض المحددة، فهي تقوم فقط بإخطار كل من يستمع إليها. عندما تفصل مراقباً، فإنه يتوقف عن تلقي التحديثات تلقائياً.

ورقة مرجعية

يحدد نمط المراقب (Observer pattern) تبعية "واحد إلى متعدد" حيث يقوم الموضوع (subject) بإخطار جميع مراقبيه تلقائيًا عند تغير حالته.

يحتوي هذا النمط على دورين رئيسيين:

  • الموضوع (Subject): يحتفظ بقائمة من المراقبين ويخطرهم بتغييرات الحالة
  • المراقب (Observer): ينفذ واجهة تحديث لتلقي الإخطارات

التنفيذ الأساسي:

#include <iostream>
#include <vector>
#include <algorithm>

class Observer {
public:
    virtual void update(int value) = 0;
    virtual ~Observer() = default;
};

class Subject {
    std::vector<Observer*> observers;
    int state = 0;
    
public:
    void attach(Observer* obs) {
        observers.push_back(obs);
    }
    
    void detach(Observer* obs) {
        observers.erase(
            std::remove(observers.begin(), observers.end(), obs),
            observers.end());
    }
    
    void setState(int value) {
        state = value;
        notify();
    }
    
    void notify() {
        for (Observer* obs : observers) {
            obs->update(state);
        }
    }
};

class Display : public Observer {
    std::string name;
public:
    Display(const std::string& n) : name(n) {}
    void update(int value) override {
        std::cout << name << " received: " << value << "\n";
    }
};

الأساليب (Methods) الرئيسية:

  • attach(): يسجل مراقبًا لتلقي الإخطارات
  • detach(): يزيل مراقبًا من قائمة الإخطارات
  • notify(): يمر عبر جميع المراقبين ويستدعي أسلوب update() الخاص بهم

استخدم نمط المراقب (Observer) عندما تتطلب التغييرات في كائن واحد تحديث كائنات أخرى دون وجود ارتباط وثيق (tight coupling) بينها.

جرّب بنفسك

#include <iostream>
#include <string>
#include "WeatherStation.h"

using namespace std;

int main() {
    // قراءة المدخلات
    string display1Name, display2Name;
    double temperature;
    
    cin >> display1Name;
    cin >> display2Name;
    cin >> temperature;
    
    // TODO: إنشاء كائن WeatherStation
    
    // TODO: إنشاء كائنين TemperatureDisplay بالأسماء المدخلة
    
    // TODO: ربط كلا الشاشتين بمحطة الأرصاد الجوية
    
    // TODO: ضبط درجة الحرارة على القيمة المدخلة
    // (يجب أن تطبع كلتا الشاشتين رسائلهما)
    
    // TODO: فصل الشاشة الأولى
    
    // TODO: ضبط درجة الحرارة لتكون أعلى بـ 5 درجات من القيمة المدخلة
    // (يجب أن تطبع الشاشة الثانية فقط)
    
    return 0;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming