צופה ונצפה

הנה הבעיה: אתה מתכנן תוכנית שתציג נתונים המתארים סצנה תלת ממדית בשני ממדים. התוכנית חייבת להיות מודולרית וחייבת לאפשר תצוגות מרובות בו זמנית של אותה סצנה. כל תצוגה חייבת להיות מסוגלת להציג את הסצנה מנקודת תצפית אחרת, בתנאי תאורה שונים. חשוב מכך, אם חלק מהסצנה הבסיסית משתנה, התצוגות חייבות לעדכן את עצמן.

אף אחת מהדרישות הללו אינה מציבה אתגר תכנותי שלא ניתן להתגבר עליו. אולם אם היה צריך לכתוב את הקוד שמטפל בכל דרישה דה נובו , זה יוסיף עבודה משמעותית למאמץ הכללי. למרבה המזל, התמיכה במשימות אלה כבר מסופקת על ידי ספריית המחלקות Java בצורת ממשק Observerומחלקה Observable- הן בהשראתן, בין השאר, מהדרישות של ארכיטקטורת MVC.

ארכיטקטורת ה- Model / View / Controller (MVC)

ארכיטקטורת המודל / תצוגה / בקר הוצגה כחלק מ- Smalltalk, שפת תכנות פופולרית מונחה עצמים שהומצאה על ידי אלן קיי. MVC תוכנן להפחית את מאמץ התכנות הנדרש לבניית מערכות המשתמשות במצגות מרובות ומסונכרנות של אותם נתונים. המאפיינים המרכזיים שלו הם כי מתייחסים למודל, לבקרים ולתצוגות כאל ישויות נפרדות, וכי השינויים שבוצעו במודל צריכים לבוא לידי ביטוי אוטומטית בכל אחת מהצפיות.

בנוסף לדוגמת התוכנית המתוארת בפסקת הפתיחה לעיל, ניתן להשתמש בארכיטקטורת Model / View / Controller לפרויקטים כגון:

  • חבילת תרשים המכילה תצוגות עמודות תרשים עמודות, תרשימי קו ותצוגת עוגה של אותם נתונים.
  • מערכת CAD, בה ניתן לצפות בחלקים מהעיצוב בהגדלות שונות, בחלונות שונים ובסולמות שונים.

איור 1 ממחיש את ארכיטקטורת ה- MVC בצורתו הכללית ביותר. יש מודל אחד. בקרים מרובים מתפעלים את המודל; תצוגות מרובות מציגות את הנתונים במודל ומשתנות עם שינוי מצב המודל.

איור 1. ארכיטקטורת המודל / תצוגה / בקר

היתרונות של MVC

לארכיטקטורת המודל / תצוגה / בקר יש כמה יתרונות:

  • יש הפרדה מוגדרת בבירור בין רכיבי התוכנית - ניתן לפתור בעיות בכל תחום באופן עצמאי.
  • יש ממשק API מוגדר היטב - כל מה שמשתמש בממשק ה- API כהלכה יכול להחליף את הדגם, את התצוגה או את הבקר.
  • הכריכה בין המודל לתצוגה הינה דינמית - היא מתרחשת בזמן הריצה, ולא בזמן הידור.

על ידי שילוב ארכיטקטורת MVC בתכנון, ניתן לתכנן חלקים של תוכנית בנפרד (ולעצב אותם כדי שיעשו את עבודתם היטב) ואז לאגד אותם יחד במהלך הריצה. אם רכיב נחשב מאוחר יותר כבלתי מתאים, ניתן להחליפו מבלי להשפיע על החלקים האחרים. ניגוד לתרחיש זה עם הגישה המונוליטית האופיינית לתוכניות Java רבות ומהירות ומלוכלכות. לעתים קרובות מסגרת מכילה את כל המצב, מטפלת בכל האירועים, עושה את כל החישובים ומציגה את התוצאה. לפיכך, במערכות כאלה מלבד הפשוטות ביותר, ביצוע שינויים לאחר מעשה אינו טריוויאלי.

הגדרת החלקים

המודל הוא האובייקט המייצג את הנתונים בתוכנית. היא מנהלת את הנתונים ומבצעת את כל התמורות בנתונים אלה. למודל אין ידע ספציפי לא לבקריו ולא לדעותיו - הוא אינו מכיל התייחסויות פנימיות לאף אחד מהם. במקום זאת, המערכת עצמה לוקחת על עצמה את האחריות לשמור על קשרים בין המודל להשקפותיו ולהודיע ​​על השקפות כאשר המודל משתנה.

התצוגה היא האובייקט שמנהל את התצוגה החזותית של הנתונים המיוצגים על ידי המודל. הוא מייצר ייצוג חזותי של אובייקט המודל ומציג את הנתונים למשתמש. הוא מתקשר עם המודל באמצעות התייחסות לאובייקט המודל עצמו.

הבקר הוא האובייקט המספק את האמצעים לאינטראקציה של המשתמשים עם הנתונים המיוצגים על ידי המודל. הוא מספק את האמצעים שבהם נעשים שינויים, במידע שבמודל או במראה הנוף. הוא מתקשר עם המודל באמצעות התייחסות לאובייקט המודל עצמו.

בשלב זה דוגמא קונקרטית עשויה להועיל. שקול כדוגמה את המערכת שתוארה בהקדמה.

איור 2. מערכת הדמיה תלת מימדית

החלק המרכזי של המערכת הוא המודל של הסצנה התלת מימדית. המודל הוא תיאור מתמטי של הקודקודים והפרצופים המרכיבים את הסצנה. ניתן לשנות את הנתונים המתארים כל קודקוד או פנים (אולי כתוצאה מקלט המשתמש או מעיוות סצנה או אלגוריתם משתנה). עם זאת, אין מושג של נקודת מבט, שיטת תצוגה (מסגרת קווית או מוצקה), פרספקטיבה או מקור אור. המודל הוא ייצוג טהור של האלמנטים המרכיבים את הסצנה.

החלק של התוכנית שהופך את הנתונים במודל לתצוגה גרפית הוא התצוגה. הנוף מגלם את התצוגה בפועל של הסצנה. זהו הייצוג הגרפי של הסצנה מנקודת מבט מסוימת, בתנאי תאורה מסוימים.

הבקר יודע מה ניתן לעשות למודל, ומיישם את ממשק המשתמש המאפשר את הפעולה הזו. בדוגמה זו, לוח בקרה להזנת נתונים עשוי לאפשר למשתמש להוסיף, לשנות או למחוק קודקודים ופנים.

צופה ונצפה

שפת Java תומכת בארכיטקטורת MVC עם שתי מחלקות:

  • Observer: כל אובייקט שרוצה לקבל הודעה כאשר ישתנה מצב של אובייקט אחר.
  • Observable: כל אובייקט שמצבו עשוי לעניין, ואובייקט אחר רשאי לרשום בו עניין.

ניתן להשתמש בשני הכיתות הללו כדי ליישם הרבה יותר מסתם ארכיטקטורת MVC. הם מתאימים לכל מערכת בה יש להודיע ​​באופן אוטומטי על אובייקטים על שינויים המתרחשים באובייקטים אחרים.

בדרך כלל, המודל הוא תת-סוג Observableוהנוף הוא תת-סוג של Observer. שתי מחלקות אלה מטפלות בפונקציית ההתראות האוטומטית של MVC. הם מספקים את המנגנון שבאמצעותו ניתן להודיע ​​באופן אוטומטי על התצוגות על שינויים במודל. הפניות לאובייקט למודל הן בבקר והן בתצוגה מאפשרות גישה לנתונים במודל.

פונקציות תצפית וניתנות לצפייה

להלן רשימות קוד למתבונן ולפונקציות הניתנות לצפייה:

מַשׁקִיף

  • public void update(Observable obs, Object obj)

    נקרא כאשר חל שינוי במצב הנצפה.

נצפה

  • public void addObserver(Observer obs)

    מוסיף צופה לרשימת המשקיפים הפנימית.

  • public void deleteObserver(Observer obs)

    מוחק משקיף מרשימת המשקיפים הפנימית.

  • public void deleteObservers()

    מוחק את כל המשקיפים מרשימת המשקיפים הפנימית.

  • public int countObservers()

    מחזירה את מספר המשקיפים ברשימת המשקיפים הפנימית.

  • protected void setChanged()

    Sets the internal flag that indicates this observable has changed state.

  • protected void clearChanged()

    Clears the internal flag that indicates this observable has changed state.

  • public boolean hasChanged()

    Returns the boolean value true if this observable has changed state.

  • public void notifyObservers()

    Checks the internal flag to see if the observable has changed state and notifies all observers.

  • public void notifyObservers(Object obj)

    Checks the internal flag to see if the observable has changed state and notifies all observers. Passes the object specified in the parameter list to the notify() method of the observer.

Next we'll take a look at how to create a new Observable and Observer class, and how to tie the two together.

Extend an observable

A new class of observable objects is created by extending class Observable. Because class Observable already implements all of the methods necessary to provide the desired behavior, the derived class need only provide some mechanism for adjusting and accessing the internal state of the observable object.

In the ObservableValue listing below, the internal state of the model is captured by the integer n. This value is accessed (and, more importantly, modified) only through public accessors. If the value is changed, the observable object invokes its own setChanged() method to indicate that the state of the model has changed. It then invokes its own notifyObservers() method in order to update all of the registered observers.

Listing 1. ObservableValue

 import java.util.Observable; public class ObservableValue extends Observable { private int n = 0; public ObservableValue(int n) { this.n = n; } public void setValue(int n) { this.n = n; setChanged(); notifyObservers(); } public int getValue() { return n; } } 

Implement an observer

A new class of objects that observe the changes in state of another object is created by implementing the Observer interface. The Observer interface requires that an update() method be provided in the new class. The update() method is called whenever the observable changes state and announces this fact by calling its notifyObservers() method. The observer should then interrogate the observable object to determine its new state, and, in the case of the MVC architecture, adjust its view appropriately.

In the following TextObserver listing, the notify() method first checks to ensure that the observable that has announced an update is the observable that this observer is observing. If it is, it then reads the observable's state, and prints the new value.

Listing 2. TextObserver

 import java.util.Observer; import java.util.Observable; public class TextObserver implements Observer { private ObservableValue ov = null; public TextObserver(ObservableValue ov) { this.ov = ov; } public void update(Observable obs, Object obj) { if (obs == ov) { System.out.println(ov.getValue()); } } } 

Tie the two together

A program notifies an observable object that an observer wishes to be notified about changes in its state by calling the observable object's addObserver() method. The addObserver() method adds the observer to the internal list of observers that should be notified if the state of the observable changes.

The example below, showing class Main, demonstrates how to use the addObserver() method to add an instance of the TextObserver class (Listing 2) to the observable list maintained by the ObservableValue class (Listing 1).

Listing 3. addObserver()

 public class Main { public Main() { ObservableValue ov = new ObservableValue(0); TextObserver to = new TextObserver(ov); ov.addObserver(to); } public static void main(String [] args) { Main m = new Main(); } } 

How it all works together

The following sequence of events describes how the interaction between an observable and an observer typically occurs within a program.

  1. First the user manipulates a user interface component representing a controller. The controller makes a change to the model via a public accessor method -- which is setValue() in the example above.
  2. The public accessor method modifies the private data, adjusts the internal state of the model, and calls its setChanged() method to indicate that its state has changed. It then calls notifyObservers() to notify the observers that it has changed. The call to notifyObservers() could also be performed elsewhere, such as in an update loop running in another thread.
  3. The update() methods on each of the observers are called, indicating that a change in state has occurred. The observers access the model's data via the model's public accessor methods and update their respective views.

Observer/Observable in an MVC architecture

Now let's consider an example demonstrating how observables and observers typically work together in an MVC architecture. Like the model in the ObservableValue (Listing 1) the model in this example is very simple. Its internal state consists of a single integer value. The state is manipulated exclusively via accessor methods like those in ObservableValue. The code for the model is found here.

Initially, a simple text view/controller class was written. The class combines the features of both a view (it textually displays the value of the current state of the model) and a controller (it allows the user to enter a new value for the state of the model). The code is found here.

By designing the system using the MVC architecture (rather than embedding the code for the model, the view, and the text controller in one monolithic class), the system is easily redesigned to handle another view and another controller. In this case, a slider view/controller class was written. The position of the slider represents the value of the current state of the model and can be adjusted by the user to set a new value for the state of the model. The code is found here.

About the author

Todd Sundsted has been writing programs since computers became available in desktop models. Though originally interested in building distributed object applications in C++, Todd moved to the Java programming language when Java became the obvious choice for that sort of thing.

This story, "Observer and Observable" was originally published by JavaWorld .