Você está na página 1de 11

Wat chin g the O b s erv a ble s: Th e D ep enden cy M e c h a ni s m

John Hunt Arkevista Ltd, Hartham Park Corsham, Wiltshire SN13 0RP

THE DEPENDENCY MECHANISM


This column presents the way in which Java implements the dependency mechanism found in Smalltalk also known as the Observer pattern (in the "Patterns" book). There are in fact a number of different relationships between objects in an Java system. Most of these are familiar to any student of object oriented programming languages: 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 as Smalltalk and Java. This is the dependency relationship, where the state or behaviour of one object is dependent on the state of another object. For example, in figure 1 the arrows indicates that there is a set of dependency

B A D

C E Tail depends on head

F
FIG..1 Dependency between objects

relationships between the objects A to F. Object A is dependent on some aspect of objects B and D. In turn object B is dependent on some aspect of object C and so on.

Why Do We WantDependency?
The reasons for dependency are all down to change . We wish to communicate the fact that an object has changed its value to another object which may be interested in either the fact of the change or the new value effected by the change. The dependency mechanism provides a way of communicating such 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 know the objects that are interested, then you can send messages to them. However, if all you know is that sometime, at a later date, some object may need to know something about the state of an object (but you do not know what that other object might be) then you cannot arrange to send messages to it. The dependency mechanism allows any object whose class is a subclass of
Observable to act as the source of a dependency. 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 dependency relationship. The (hidden) dependency mechanism takes care of informing the unknown objects about the updates.

How Does DependencyWork?


The dependency mechanism is implemented in the class Observable and the interface Observer within the java.util package. This class is a direct subclass of Object, so any class can inherit from Observable and thus take part in a dependency relationship. In turn, a Java class can implement zero of more interfaces, thus an observer class can implement the Observer interface and receive notifications of changes to the Observable object. In Java terminology, the head of the dependent 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 objects state by the notifyObservers method.

You can browse the Observable class to explore the dependency mechanism. The basic implementation, inherited from Observable, associates a vector of other objects with the observable object. This vector holds the objects which are dependent on the object (collectively, these objects are known as the objects observers). For example, in Figure 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 as it is private to the Observable class. However,

it can obtain the number of observers using the countObservers method.

obs

ObjectA

ObjectB

ObjectC

FIG. 2 An object and its observers

Constructing Dependencies
The addObserver message adds an object to a dependency list. For example, we can construct the above dependencies:
ObjectA.addObserver(ObjectB); ObjectA.addObserver(ObjectC);

Duplicates cannot be held in the list of observers. If you attempt to add an object to the list of observers more than once, it is only recorded once (and thus is only told of changes once). An observable object holds a vector of objects that depend on 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 in Figure 17.2. This may seem a bit strange at first, however, once you understand how the dependency mechanism works, as realized by the Observable class and the Observer interface, you will see why things are this way round. You can remove dependencies once they have been created. The following code removes ObjectB from the observer list of ObjectA:
ObjectA.deleteObserver(ObjectB);

A SIMPLE DEPENDENCY EXAMPLE


We develop further the following very simple dependency example during the chapter. It creates two objects and a dependency between them. The objects are instances of the classes DataObject and ObserverObject, which are direct subclasses of
Observable and Object, respectively. Place the classes in appropriate .java files

(i.e. DataObject.java and ObserverObject.java).


import java.util.Observable; public class DataObject extends Observable { }

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 dependency relationship. To illustrate how objects of these classes can be related, define the following class in a file called TestHarness.java. Although, in general, I do not condone using separate objects to illustrate how other objects work, in this case, it ensures that you understand that the dependency 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 println is the value 1, although our DataObject class is empty! From the point of view of the DataObject class, dependency is an invisible mechanism that works behind the scenes. Of course, this is not really the case. The

dependency mechanism has been inherited from Observable and is implemented via message sends and method executions just like any behaviour provided by an object.

MAKING DEPENDENCY WORK FOR YOU


We have now considered how to construct a dependency 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.
obs 4. update message

1. setChanged(); 2. notifyObservers();

ObjectA

3. update message ObjectB

ObjectC

FIG. 3 The dependency mechanism in action

Figure 3 illustrates the sequence of messages which are sent in response 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. In fact, so much so that a 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 dependency framework, it can make more sense. The change message is a message to the dependency mechanism asking it to notify the objects dependants about a change. The dependency mechanism is inherited and is generic across applications, but

system developers cannot know when the change message should be sent that is application specific. It is, therefore, the application developers 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. As 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).

TheChangedMethods
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 objects execution (when data is entered), but trigger the notification at another point (at the end of some execution cycle). Interestingly, the Smalltalk dependency 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.
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 the 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 the 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. The notifyObservers messages triggers off the update part of the dependency mechanism. The only difference between the messages relates to 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 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 which must be implemented by objects which wish to take part in a dependency :
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 DEPENDENCY EXAMPLE


This section provides an example of how the dependency mechanism works. We use the DataObject and ObserverObject classes defined earlier. The first thing we do is to define some instance variables ( age, name and address), a constructor and an updater, age, in DataObject:
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; } public void age (int years) { age = years; setChanged(); notifyObservers("age"); } public String toString() { return "(DataObject: " + name + " of " + address + " who is " + age + ")"; } }

The updater method, age, which sets the age instance variable, also informs itself that it has changed (using setChanged) and that this fact should be passed onto its dependants (using notifyObservers(age)). This is a typical usage of the
notifyObservers message. That is, it informs the notification mechanism about the

type of change which has taken place. It also illustrates good style: it informs this object about the change which 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. It implies that something outside the DataObject decides when to inform its observers. Next we define how an instance of ObserverObject responds to the change in a
DataObject; we define an update method:
import java.util.Observer; import java.util.Observable; 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); } }

As this is just 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 objects age:
public class TestHarness { 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); } }

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 above, 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. It can be difficult to debug and maintain relationships that have been implemented using the dependency mechanism (as the message chain is partly hidden). Therefore,

you should exercise care in its use. However, if used appropriately (and clearly documented) the dependency mechanism is Java is a very powerful construct indeed.

SUMMARY
This column has introduced the dependency mechanism in Java as implemented by the Observable class and Observer interface. This relationship is quite complex but the user interface classes make extensive use of it and you should gain some experience with it.

REFERENCES
Design Patterns: Elements of Reusable Object-Oreinted Software, E. Gamma, R. Helm, R. Johnson and J. Vlissides, Addison-Wesley, 0-201-63361-2, 1995.

Observers and Observables

11

KEY POINTS
Observers and Observables support dependency relationships Observable class is a direct subclass of Object Observer interface allows objects to receive updates Changed methods notify observers of changes SetChanged method indicates if an actual change has occurred May be difficult to maintain or debug

Você também pode gostar