תבנית Observer
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 94 מתוך 104.
תבנית 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";
}
};
int main() {
Subject sensor;
Display screen1("Screen1"), screen2("Screen2");
sensor.attach(&screen1);
sensor.attach(&screen2);
sensor.setState(42); // שני הצגים קיבלו הודעה
}כאשר קוראים ל־setState(), הנושא עובר על כל הצופים הרשומים וקורא למתודת update() שלהם. צופים יכולים להתחבר או להתנתק בכל עת, וכך המערכת גמישה ובעלת צימוד רופף.
השתמשו ב-Observer כאשר שינויים באובייקט אחד מחייבים לעדכן אובייקטים אחרים, ואינכם רוצים שהאובייקטים האלה יהיו מצומדים זה לזה באופן הדוק.
אתגר
קלבואו נבנה מערכת ניטור של תחנת מזג אוויר באמצעות תבנית Observer. תיצרו תחנת מזג אוויר שעוקבת אחר נתוני הטמפרטורה ומודיעה אוטומטית לכמה יחידות תצוגה בכל פעם שהטמפרטורה משתנה: זהו יישום קלאסי של התבנית בעולם האמיתי.
תארגנו את הקוד בשלושה קבצים:
Observer.h: הגדירו את ממשק הצופה ואת צופה התצוגה הקונקרטי.צרו מחלקה מופשטת בשם
Observerעם שיטה וירטואלית טהורה בשםupdate(double temperature), שהצופים יממשו כדי לקבל התראות, וכן עם הורס וירטואלי.לאחר מכן, צרו מחלקה בשם
TemperatureDisplayשיורשת מ־Observer. לכל תצוגה יש שם (מחרוזת) שנקבע דרך הבנאי שלה. כשקוראים ל־update(), היא אמורה להדפיס:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: צרו את הנושא שמנהל את הטמפרטורה ומודיע לצופים.המחלקה
WeatherStationשלכם צריכה:- לשמור רשימה של מצביעים לצופים ואת הטמפרטורה הנוכחית (אתחלו אותה ל־
0.0) - לכלול שיטה בשם
attach(Observer* obs)לרישום צופים - לכלול שיטה בשם
detach(Observer* obs)להסרת צופים - לכלול שיטה בשם
setTemperature(double temp)שמעדכנת את הטמפרטורה ומודיעה לכל הצופים המצורפים - לכלול שיטה פרטית בשם
notify()שקוראת ל־update()אצל כל צופה עם הטמפרטורה הנוכחית
- לשמור רשימה של מצביעים לצופים ואת הטמפרטורה הנוכחית (אתחלו אותה ל־
main.cpp: הדגימו את תבנית Observer בפעולה.קראו שלושה ערכי קלט:
- שם לתצוגה הראשונה (מחרוזת)
- שם לתצוגה השנייה (מחרוזת)
- ערך טמפרטורה (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שימו לב כיצד תבנית Observer יוצרת מערכת עם צימוד רופף: תחנת מזג האוויר לא צריכה לדעת דבר על התצוגות הספציפיות, אלא רק להודיע למי שמאזין. כשאתם מנתקים צופה, הוא מפסיק לקבל עדכונים באופן אוטומטי.
נסו בעצמכם
#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;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים10סקירה כללית של STL
סקירה כללית והפילוסופיה של STLקונטיינרים של STLאיטרטוריםאלגוריתמים של STLפונקטורים וביטויי למבדהסיכום – תדירות מילים13תבניות עיצוב חלק 1
מבוא לתבניות עיצובתבנית SingletonFactory ו־Abstract Factoryתבנית Builderתבנית Observerתבנית Strategy2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין