Download Watching the Observables: The Notification Mechanism

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
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