Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
JAVA JOLT Watching the Observables: The Notification Mechanism T HIS COLUMN shows how Java implements the notification mechanism found in Smalltalk, which is also known as the Observer pattern in the Patterns book.1 There are a number of different relationships between objects in a Java system. Most of these are familiar to any student of object-oriented programming languages. They are: Inheritance (class-to-class relationships). Instantiation (class-to-instance relationships). Part-of or contains (instance-to-instance relationships). ● ● ● However, there is another important relationship supported by many object-oriented languages, such B C E A D Tail depends on head Observer F pattern. John Hunt explains how it works 70 The notification mechanism is implemented in the class Observable and the interface Observer within the java.util package. Observable is a direct subclass of Object, so any class can inherit from Observable and thus take part in a notification relationship. In turn, a Java class can implement zero or more interfaces, thus an observer class can implement the Observer interface and receive notifications of changes to the Observable Fig 1. as Smalltalk and Java. This is the notification relationship, where the state or behaviour of one object is dependent on the state of another object. For example, in fig 1 the arrows indicate that there is a set of notification relationships between the objects A through F. Object A is dependent on some aspect of objects B and D, and in turn, object B is dependent on some aspect of object C, and so on. Why do we want notification? Dr Hunt is a partner of JayDee Technology,a Java specialist company,and can be contacted at [email protected] How does notification work? The reasons for notification all come down to change. We wish to communicate that an object has changed its value to another object, which may either be interested in the event of the change or in the new value effected by the change. The notification mechanism provides a way of communicating such object. FACTS AT A GLANCE ● Observers and Observables support dependency relationships. ● The Observable class is a direct subclass of Object. ● The Observer interface allows objects to receive updates. ● Changed methods notify observers of changes. ● The SetChanged method indicates whether an actual change has occurred. ● Relationships may be difficult to maintain or debug. APPLICATION DEVELOPMENT ADVISOR ● www.appdevadvisor.com © Nip Rogers / SIS The notification or dependency mechanism in Java has its counterpart in Smalltalk and is also known as the Notification Between Objects events in a generic, implementation-independent manner. An obvious question is: “Why not just get the object to send messages to those interested in it?” The answer is that if you don’t yet know which objects is interested then you cannot arrange to send messages to it. The notification mechanism allows any object whose class is a subclass of Observable to act as the source of a notification. Any object that implements the Observer interface can act as the dependent object. We do not need to know what might be interested in the object. We merely need to know that it might be involved in a notification relationship. The (hidden) notification mechanism takes care of informing the currently unknown objects about the updates. JAVA JOLT In Java terminology, the head of the notification relationship (i.e., the object on which other objects depend) is referred to as the observable object, while the dependent object is referred to as the observer object. The observable object allows other objects to observe its current state. An observable object can have zero or more observers, which are notified of changes to the object’s state by the notifyObservers method. You can browse the Observable class to explore the notification mechanism. The basic implementation, inherited from Observable, associates a vector of other objects with the observable object. An Object and its Observers A simple notification example import java.util.Observable; public class DataObject extends Observable { } <<observer>> ObjectB <<observer>> ObjectC Fig 2. In Java terminology, the head of the notification relationship (i.e., the object on which other objects depend) is referred to as the observable object, while the dependent object is referred to as the observer object This vector holds the objects that are dependent on the object. (Collectively, these objects are known as the object’s observer dependants.) For example, in fig 2, the object ObjectA has two observers: ObjectB and ObjectC. The links to the dependent objects are held by ObjectA in a list of observers called obs. ObjectA cannot access this vector because it is private to the Observable class; however, it can obtain the number of observers using the countObservers method. Constructing notifications The addObserver message adds an object to a notification list. For example, we can construct the following notifications: ObjectA.addObserver(ObjectB); ObjectA.addObserver(ObjectC); Duplicates cannot be held in the list of observers. If you try to add an object to the list of observers more than once, it will only be recorded once (and thus will only be told of changes once). An observable object holds a vector of objects that depend on SEPTEMBER–OCTOBER 1999 ObjectA.deleteObserver(ObjectB); We will develop further the following very simple notification example during the remainder of this column. This example creates two objects and a notification between them. The objects are instances of the classes DataObject and ObserverObject, which are direct subclasses of Observable and Object, respectively. obs <<observable>> ObjectA it, but an object cannot access information about the objects on which it depends. For example, there are no references from ObjectB or ObjectC back to ObjectA. This may seem a bit strange at first, but once you understand how the notification mechanism works, as it is realised by the Observable class and the Observer interface, you will see why things are this way. You can remove notifications once they have been created. The following code removes ObjectB from the observer list of ObjectA: The update method is explained below; it is merely used here to allow the ObserverObject to implement the Observer interface: import java.util.Observer; import java.util.Observable; public class ObserverObject implements Observer { public void update(Observable o, Object arg){ System.out.println(“Object “ + o + “ has changed”); } } We now have two classes that can take part in a notification relationship. The following test harness illustrates how objects of these classes can be related. Although, in general, I do not condone using separate objects to illustrate how other objects work, in this case it will ensure that you understand that the notification relationships are handled automatically via the inherited facilities. public class TestHarness { public static void main(String args []) { TestHarness t = new TestHarness(); t.test(); } public void test () { DataObject temp1 = new DataObject(); ObserverObject temp2 = new ObserverObject(); temp1.addObserver(temp2); System.out.println(temp1.countObservers()); } } The result of the println method above, is the value 1, although our DataObject class is empty. From the point of view of the DataObject class, notification is an invisible mechanism that works behind the scenes. Of course, this is not really the case. The notification mechanism has been inherited from Observable and is implemented via message sends and method executions just like any behaviour provided by an object. 71 JAVA JOLT Making notification work for you We have now considered how to construct a notification relationship. We want this relationship to inform the dependent objects that a change has occurred in the object on which they depend. To do this we use two sets of methods. One set, the ‘changed’ methods, states that something has changed. The other set, the ‘update’ methods, is used to state what type of update is required. Fig 3 illustrates the sequence of messages that are sent when an object changes. That is, when ObjectA is sent the setChanged and notifyObservers messages (usually by itself) all its observers are sent an update message. From the point of view of ObjectA, much of this behaviour is hidden—so much so that one common point of confusion relates to the sending of one message (the notifyObservers message) and the execution of another method (the update method). A programmer defining objects A, B, and C: ● ● ● Sends one (or more) setChanged messages to ObjectA. Sends a notifyObservers message to ObjectA. Defines an update method in ObjectB and ObjectC. The confusion stems from the need to send one message but define another. However, if you think about how you are linking into the existing notification framework, it can make more sense. The change message is a message to the notification mechanism asking it to notify the object’s dependants about a change. The notification mechanism is inherited from framework classes and is generic across applications, but framework developers cannot know when the setChanged and notifyObservers messages should be sent to Observable objects—that is application-specific. It is, therefore, the application developer’s responsibility to send the change messages. For example, you may only want dependants to be told of certain changes, such as updates to one field on an input screen, etc. Similarly, there is no way that the system developers can know how the dependants should update themselves. The update message could display the new value produced by the originating object, perform some calculation, or access a database. Because the update methods are defined in an interface, they do nothing; they are abstract methods. Nothing is said anywhere in the system about what an observer should do when the object changes. In the simple example above, we need to specify what ObjectB and ObjectC should do when ObjectA changes. This requires defining update methods (as we did very briefly in the ObserverObject example presented earlier). The Changed methods There are three messages that inform an object that it has changed and should notify its observers: ● setChanged() indicates to the observable object that something has happened which should be passed onto any observers, next time they are notified of a change. It is very useful to separate out specifying that something has changed from the actual notification action. You can determine that observers must be notified at one point in an object’s execution (when data is entered), but trigger the notification at another point (at the end of some execution cycle). Interestingly, the Smalltalk notification mechanism does not provide this flexibility. ● notifyObservers() informs the observers that something has changed (but not what the change was). It calls the notifyObservers(Object object) method with a null parameter. Thus it merely indicates that a change has taken place, as the parameter object is used to 72 indicate the exact nature of the change. ● notifyObservers(Object object) notifies the observers that something has changed and what the change was. This is done by sending the update message to the objects in obs vector. The update method takes two parameters: the current object and the object passed into the notifyObservers method. It assumes that the change can be represented as an object, so if the change results in a number 24, it must be wrapped in an integer object. The first point to note about the changed messages are that they are sent to the object which has changed in some way. They inform the object that it has changed and that this change should be passed onto any observers. The changed messages do not effect the change or notify the observers. Both the notifyObservers messages trigger off the update part of the notification mechanism. The only difference between the messages is the amount of information provided. The simplest notification message (and the one with the least information) is the notifyObservers() message. This can be useful when you want to make sure that the dependants assume nothing about the object to which the change is happening. The result of executing this class is illustrated below: C java TestHarness 1 Object (DataObject: John of C47 who is 35) has changed its age Need to ensure that the font and layout indicate which part of the text is console output. The way these messages are implemented is that the setChanged method sets a Boolean flag, changed, to true. This flag is examined by the notifyObservers(Object) method; if the flag is set to true, it notifies the objects in the obs vector that they need to update themselves and resets the changed flag to false. If the changed flag is already false, it does nothing. The Observer interface The Observer interface defines the abstract update method that must be implemented by objects that wish to take part in a notification: public interface Observer { void update(Observable observable, Object arg); } As with all interfaces, any concrete class that implements this interface must provide the body of the update method. Any class implementing this interface can be guaranteed to work with the notification methods used in an observer object. The first parameter passed to this method is the observable object. If ObjectA is sent a setChanged message followed by a notifyObservers message, then ObjectB and ObjectC are sent the update message with the first parameter set to ObjectA. The value of the arg parameter depends on the version of notifyObservers which was sent to ObjectA. If no parameters were sent, then the value of arg is null. If one parameter was sent, arg holds the parameter. This means that the developer can decide how much information the observer object can work with. Extending the notification example This section provides an example of how the notification mechanism works. We will use the DataObject and ObserverObject classes that we defined earlier. APPLICATION DEVELOPMENT ADVISOR ● www.appdevadvisor.com JAVA JOLT First, we will define some instance variables (age, name, and address), a constructor, and an updater (age), in DataObject: The Notification Mechanism in Action obs import java.util.Observable; public class DataObject extends Observable { String name = “”; int age = 0; String address = “”; public DataObject (String aName, int years, String anAddress) { name = aName; age = years; address = anAddress; } 3. update message 1. setChanged(); 2. notifyObservers(); public static void main(String args []) { TestHarness t = new TestHarness(); t.test(); } public void test () { DataObject temp1 = new DataObject(“John”, 34, “C47”); ObserverObject temp2 = new ObserverObject(); temp1.addObserver(temp2); System.out.println(temp1.countObservers()); temp1.age(35); } } public class ObserverObject implements Observer { public void update(Observable o, Object arg){ if (arg == “age”) System.out.println(“Object “ + o + “ has changed its “ + arg); else System.out.println(“Don’t know how to handle changes to “ + arg); } } Because this is a simple example, all it does is print a string on the console that reports the change. We use the TestHarness class we defined earlier to try out this simple example. We use the new DataObject constructor to create the observable object and the age updater to change the observable object’s age: SEPTEMBER–OCTOBER 1999 <<observer>> ObjectC public class TestHarness { public String toString() { return “(DataObject: “ + name + “ of “ + address + “ who is “ + age + “)”; } import java.util.Observer; import java.util.Observable; <<observer>> ObjectB Fig 3. public void age (int years) { age = years; setChanged(); notifyObservers(“age”); } The updater method, age, which sets the age instance variable, also informs itself that it has changed (using setChanged) and that its dependants should be notified of this change (using notifyObservers(“age”)). This is a typical usage of the notifyObservers message. That is, it informs the notification mechanism about the type of change that has taken place. It also illustrates good style: It informs this object about the change that has taken place. It is very poor style to have an object send an instance of DataObject the message age, followed by the changed messages, because that implies that something outside the DataObject decides when to inform its observers. Next, we will define how an instance of ObserverObject responds to the change in a DataObject. We define an update method: <<observable>> ObjectA 4. update message } The result of executing this class is illustrated below: C >java TestHarness 1 Object (DataObject: John of C47 who is 35) has changed its age The ObserverObject has been informed of the change of age, although we did not define a method to do this directly. In the simple example presented here, you should note (and understand) the following points: ● DataObject does not have a reference to, nor does it know anything about, an ObserverObject. ● The ObserverObject does not reference a DataObject internally. ● The link between temp1 and temp2 is external to both objects (but internal to the Observable part of the DataObject). Conclusion It can be difficult to debug and maintain relationships that have been implemented using the notification mechanism (because the message chain is partly hidden). Therefore, you should exercise care in its use. However, if used appropriately (and clearly documented), the notification mechanism in Java is a very powerful construct indeed. ■ Reference 1. Gamma, E., et al. Design Patterns: Elements of Reusable Object-Oriented Software, Addison–Wesley, 1995. 73