תבנית Observer
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Java של Coddy. שיעור 68 מתוך 87.
תבנית המתבונן היא תבנית עיצוב התנהגותית שיוצרת קשר של אחד לרבים בין אובייקטים. כאשר אובייקט אחד (הנושא) משנה מצב, כל התלויים בו (המתבוננים) מקבלים הודעה ומתעדכנים באופן אוטומטי. אפשר לחשוב על כך כמו על הרשמה לניוזלטר: המנויים מקבלים עדכונים בכל פעם שמתפרסם תוכן חדש.
התבנית כוללת שני רכיבים עיקריים: נושא שמנהל רשימה של צופים ומודיע להם על שינויים, וצופים שמגדירים שיטת עדכון כדי להגיב להודעות:
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: חדשות מתפרצות!
// אימייל אל bob@mail.com: חדשות מתפרצות!תבנית Observer מקדמת צימוד רופף: הנושא לא צריך להכיר את המחלקות הקונקרטיות של הצופים שלו, אלא רק לדעת שהם מממשים את הממשק Observer. כך קל להוסיף סוגים חדשים של צופים בלי לשנות את הנושא.
אתגר
קלבואו נבנה מערכת להתראות על מחירי מניות באמצעות תבנית Observer! תיצרו מערכת שבה טיקר מניות מודיע למספר משקיעים בכל פעם שמחיר המניה משתנה, וכך כל משקיע יכול להגיב לעדכוני המחיר בדרכו.
תארגנו את הקוד שלכם בארבעה קבצים:
Observer.java: הגדירו את הממשקObserverשכל המשקיעים יממשו. עליו להצהיר על מתודה אחת בלבד:update(String stockName, double price), שבה צופים משתמשים כדי לקבל התראות על שינויים במחיר.StockTicker.java: צרו את מחלקת הנושא, שמתחזקת רשימה של צופים ומודיעה להם על שינויים במחיר. למחלקהStockTickerשלכם צריכים להיות:שדה String פרטי עבור שם המניה (נקבע באמצעות הבנאי).
שדה double פרטי עבור המחיר הנוכחי (בתחילה 0).
רשימה פרטית לשמירת צופים שנרשמו.
מתודת
subscribe(Observer observer)שמוסיפה צופה לרשימה.מתודת
unsubscribe(Observer observer)שמסירה צופה מהרשימה.מתודת
setPrice(double price)שמעדכנת את המחיר ולאחר מכן מודיעה לכל הצופים באמצעות קריאה למתודתupdateשלהם עם שם המניה והמחיר החדש.Investors.java: צרו שני סוגים שונים של משקיעים שמממשים את הממשקObserver:DayTrader- כולל שדה name פרטי שנקבע באמצעות הבנאי. בעת קבלת עדכון, הוא מדפיס[name] received alert: [stockName] is now $[price] - Making quick trade!LongTermInvestor- כולל שדה name פרטי שנקבע באמצעות הבנאי. בעת קבלת עדכון, הוא מדפיס[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: ל-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 למעקב אחר הטיקר
// TODO: הגדירו את המחיר ל-firstPrice (שני הצופים יקבלו הודעה)
// TODO: בטלו את המינוי של Alice
// TODO: הגדירו את המחיר ל-secondPrice (רק Bob יקבל הודעה)
scanner.close();
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמבוא לתכנות מונחה עצמיםמחלקות לעומת אובייקטיםמילת המפתח thisמתודותשדות (מאפיינים)מתודת הבנאיהעמסת בנאיםחזרה – מחשבון פשוט4ירושה
ירושה בסיסית (extends)מילת המפתח superדריסת מתודות (@Override)שרשור בנאיםהמחלקה Objectירושה יחידה ורב־רמתיתלמה אין ירושה מרובה של מחלקותסיכום – היררכיית עובדים7מתודות מיוחדות ומחלקת Object
מתודת toString()equals() ו-hashCode()מתודת clone()compareTo() ו-Comparableהממשק Comparatorסיכום – מיון מותאם אישית2מצייני גישה ואנקפסולציה
סקירה של רמות הגישהמתודות Getter ו-Setterהסתרת מידעמילת המפתח finalסיכום – מנהל חשבון בנק5פולימורפיזם
יסודות העמסת מתודותדריסת מתודות (בזמן ריצה)המרה כלפי מעלה וכלפי מטההאופרטור instanceofמחלקות ומתודות מופשטותסיכום – מחשבון צורות8מושגי OOP מתקדמים
הרכבה מול ירושהאגרגציה מול הרכבהמחלקות פנימיות, מקוננות ואנונימיותEnums ומתודות של EnumRecords (Java 16+)מחלקות חתומות (Java 17+)11תבניות עיצוב חלק 1
מבוא לתבניות עיצובתבנית Singletonתבנית Factoryתבנית Builderתבנית Observerתבנית Strategy3מאפייני מחלקה וחברים סטטיים
משתני מופע לעומת משתנים סטטייםמתודות סטטיותבלוקים סטטייםקבועים (static final)סיכום — מונה ומחלקת עזר6ממשקים ומחלקות מופשטות
מבוא לממשקיםמימוש ממשקיםמימוש של כמה ממשקיםמתודות ברירת מחדל וסטטיות בממשקמחלקות מופשטות לעומת ממשקיםממשקים פונקציונלייםסיכום – מערכת תשלומיםתרגלו בעצמכם: קומפיילר Java אונליין