Osberver – גרסת הבמאי
טרחתם, עשיתם חזרות, שיננתם את הטקסט ואחרי משהו כמו חמש שניות הבמאי קוטע את הדרמה בדיוק לפני הקטע הטוב ואומר (באנגלית זה נשמע יותר טוב): Don’t call us we’ll call you.
אמנם יש בזה משהו מעצבן, אבל מצד שני זה גם הגיוני ויעיל – אתם עצמכם לא רוצים להתקשר שלוש פעמים ביום כדי לבדוק אם יש תשובה, וגם הבמאי לא רוצה להיות עסוק במענה לכל הטלפונים של המועמדים כפול שלוש פעמים ביום.
עדיף שהוא יגבש החלטה, וכאשר יש שינוי כלשהו – הוא יעדכן את כל מי שצריך לעדכן.
ה- Design pattern שמטפל בבעיה הזו נקרא – Observer. כלומר – צופה. זוהי למעשה מערכת שמבטאת קשר של יחיד לרבים. היחיד הוא הרכיב שבו מתבצע השינוי, זה יכול להיות כפתור, אובייקט של עיתון שמודיע למנויים על מהדורה חדשה – או במאי…
הרבים הם אלו ש’נרשמים’, הצופים.
אז איך מממשים את הרעיון הזה? אז כאמור אנחנו צריכים שני סוגים של אובייקטים. הראשון הוא הנושא המרכזי שבו מתרחשים השינויים. ניתן לו את השם Subject. או יותר נכון, בגלל שאנחנו רוצים לתכנת מול אינטרפייסים הוא ייקרא – Isubject. הוא אמור להכיל שלושה מתודות: להוסיף צופה, להסיר צופה, וליידע את כל הצופים.
במימוש נחזיק List של observers ואליה נוסיף או נוריד observer. כשנרצה ליידע את כל הobservers שלנו נרוץ בלולאה על פני כל הlist הזה ונקרא למתודת update.
למעשה כבר עכשיו אנחנו מבינים את הסוג השני של האובייקט. הוא יהיה אינטרפייס בשם IObserver והוא יממש מתודה בשם Update.
בקוד האינטרפייס Isubject יראה ככה:
public interface ISubject
{
void AddObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void Notify();
}
מימוש אפשרי יראה ככה:
public class Subject : ISubject
{
public int State { get; set; } = 0;
private List<IObserver> _observers = new();
public void AddObserver(IObserver observer)
{
Console.WriteLine("Add an observer to subject.");
_observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
Console.WriteLine("Remove an observer from subject.");
}
public void Notify()
{
Console.WriteLine("Subject notifying observers...");
foreach (var observer in _observers)
{
observer.Update();
}
}
public void SetValue(int value)
{
Console.WriteLine($"The new state on the subject is {value}");
Notify();
}
}
האינטרפייס של הobservers יראה ככה:
public interface IObserver
{
void Update();
}
ומימוש אפשרי שלו יראה ככה:
internal class ObserverA : IObserver
{
public void Update()
{
Console.WriteLine("ObserverA got notified.");
}
}
אם נרצה לשלב את כל הרכיבים בקוד, זה יראה ככה:
class Program
{
static void Main(string[] args)
{
var subject = new Subject();
var observerA = new ObserverA();
subject.AddObserver(observerA);
var observerB = new ObserverB();
subject.AddObserver(observerB);
subject.SetValue(5);
subject.SetValue(9);
subject.RemoveObserver(observerB);
subject.SetValue(0);
}
}
דילמה
נישאר לנו נושא אחרון לחשוב עליו.
מתודת notify מיועדת להודיע ל observers שמשהו השתנה.
האם המתודה גם תשלח את השינוי עצמו? משהו שיראה ככה:
public void Notify()
{
Console.WriteLine("Subject notifying observers...");
foreach (var observer in _observers)
{
observer.Update(newState);
}
}
או שהיא רק תודיע על עצם השינוי, ואז למעשה מתפקידו של כל observer לפנות את ה subject, נניח בעזרת מתודה חדשה בשם GetValue שממומשת בתוך כל subject ספציפי (ולא באינטרפייס כי כל subject עשוי לרצות להודיע על סוג אחר של שינוי.
משהו שיראה ככה:
public void Notify()
{
Console.WriteLine("Subject notifying observers...");
foreach (var observer in _observers)
{
observer.Update();
//בלי ערכים שנשלחים
}
}
ואז משהו כזה:
internal class ObserverA : IObserver
{
public void Update()
{
Subject subject = new Subject();
var newValue = subject.GetNewValue();
}
}
לשתי הגישות האלה יש שמות:
Push ו Pull.
היתרון ב Push (דחיפת מידע) הוא שה observer לא מכיר את הרכיבים של הקלאס הראשי וכך נשמר העיקרון של loosely coupled – אין היכרות יותר מדי ‘אינטימית’ עם גורמי המחלקות האחרות.
מצד שני החסרון הוא בכך שהמערכת לא כל כך גמישה. כאשר יתווספו סוגים חדשים של מידע נצטרך לשנות את כל הobservers .
במקביל, גם לגישה של Pull (הודעה על שינוי, וכל observer לוקח לבד את הנתונים הרלוונטים לו) ישנו יתרון וחיסרון.
היתרון הוא הגמישות – אפשר להוסיף עוד ועוד מתודות.
החיסרון הוא הקשר ההדוק מדי בין הobservers ל Subject.
לטעמי עדיף להשתמש בPull בגלל הגמישות שהוא מייצר. עיקרון חשוב בתכנון הוא לחשוב על הגורמים בעלי הסבירות הגבוהה לשינוי עתידי, ולכן הייתי מעדיף לאפשר עוד שינויים בעתיד, גם במחיר של צימוד מסויים בין אובייקטים שלא אמורים להכיר כמעט האחד את השני.
פינת הידע הכללי
לחובבי Design Patterns המושג Gang of Four – כנופיית הארבעה, הוא מושג חביב המסמן את ארבעת החברים אריך גאמה, ריצ’רד הלם, רלף ג’ונסון וג’ון ויליסידס, שהכניסו לחיינו את המושג Design Patterns.
אולם למעשה, השימוש הראשוני (שאני מכיר) של המושג כנופיית הארבעה, הוא שימוש לא נחמד בכלל.
כנופיית הארבעה היו ארבעה חברים בכירים ורצחניים במפלגה הסינית הקומוניסטית בשנות ה-60 וה-70.
אפשר לשמוע עוד על כנופיית הארבעה בסין בפודקאסט המשחק הגדול ובפודקאסט כשבגרוש היה חור.
ולסיום – אם במהפכות עסקינן, תהנו לכם משיר נפלא על מהפכות: