Download Keeping Objects In Sync

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
Observer/Observable
Keeping Objects In Sync
Jim Coker, MageLang Institute
February 1997
One of the nice things about programming in Java is that the language and its class library are
designed to provide practical solutions to real object-oriented programming problems. In the
object programming community, many of these solutions have been well documented as design
patterns; each pattern is a generic solution to a common problem. One such pattern is known as
the Observer pattern, which is a solution to the updating problem that arises when some objects
have a dependency relationship with others. Java provides a ready-made implementation for
this pattern through the Observable class and the Observer interface.
However, one occasionally runs into design issues that require careful thinking in order to
create a solution appropriate to the task at hand. This article discusses the use of the
Observable class, and the Observer interface, and shows how to solve some potential
problems along the way.
Introducing Observer Patterns
A common scenario for using the Observer pattern involves a subject that has multiple views.
Each view object needs to be updated whenever the subject changes. One example is a drawing
program, where the subject is the internal representation of the drawing and views are different
windows opened on the drawing. Any time you make a change in the drawing, each of the
windows needs to be updated.
The Observer pattern provides a way for each of the views to be notified whenever the subject
has changed, without requiring that the subject know anything about the views, other than that
they are observers. If the subject has to know more about the views, the program can quickly
become difficult to manage--the code in the subject that handles updates becomes dependent on
each of the views it must support.
The Observer pattern solves this update problem as follows: The Subject (the object being
observed) maintains a list of its observers. Whenever the subject makes a change to itself, it
notifies all observers that a change has been made. It might also provide some generic change
information with that notification. Each view gets the same notification. Usually this
notification takes the form of a method call on the observer, with update information stored as a
parameter to that call. The only thing that the subject knows about an observer is that it
understands that method call.
An Observer Pattern Implementation in Java
The Java utilities package provides a ready-made implementation of the Observer pattern with
the Observable class, which implements the updating behavior of the Subject and the
Observer interface, which contains the update method to be called by the Observable, and can
be easily implemented by any candidate observer objects. Here are the interfaces for
Observable and Observer, with their methods grouped according to their function:
public class java.util.Observable
{
// Constructors
public Observable();
// here are methods for maintaining a
// list of observers
public void addObserver(Observer o);
public int countObservers();
public void deleteObserver(Observer o);
public void deleteObservers();
// methods for
protected void
protected void
public boolean
maintaining a changed flag
clearChanged();
setChanged();
hasChanged();
// methods for notifying observers of a change
public void notifyObservers();
public void notifyObservers(Object arg);
}
public interface java.util.Observer
{
/** this method will be called by an observable
class whenever it wants to notify this observer
of a change. The second argument can be used
to pass information about the change to the
observer */
public abstract void update(Observable o, Object arg);
}
A Simplest Possible Example
Here is a very small example showing how the Observer pattern can be implemented using
Observable and Observer. First create a Subject class with a simple data value that inherits
from Observable.
public class Subject extends Observable {
private String value;
public void setValue(String s) {
value = s;
setChanged();
// set changed flag
notifyObservers(value); // do notification
}
}
When the Subject is changed through the setValue method two calls need to be made to
ensure notification of all observers, one to set the changed flag, the other to notify observers. If
the changed flag is not set, the notifyObservers call will do nothing. When
notifyObservers is called, all observer objects will have their update method called. The
Subject has no detailed information on the observers at all; it only has to make these two calls
when it changes.
Next a View class is created that accepts notifications whenever its Subject is updated. For this
example, the View class just prints out a message.
class View implements Observer {
public void update(Observable o, Object str) {
System.out.println("update: " + str.toString());
}
}
Finally, you need to create a Subject and View, and link them together. This main method can
be inserted as a method in the Subject class in order to run it as a standalone application.
public static void main(String[] args) {
Subject s = new Subject();
View v = new View();
s.addObserver(v);
// calling setValue on the subject
// will trigger an update call to the view
s.setValue("test value");
}
Writing your own Observer-Observable
Consider the following Java example:
public interface Subject {
public void addObserver( Observer o );
public void removeObserver( Observer o );
}
In the code above, the Subject interface defines the methods that a Subject must implement in
order for Observers to add and remove themselves from the Subject.
public interface Observer {
public void update( Subject o );
}
The Observer interface (above) lists the methods that an Observer must implement so that a
Subject can send an update notification to the Observer.
Let's consider a simple implementation of Subject -- an IntegerDataBag:
import java.util.ArrayList;
import java.util.Iterator;
public class IntegerDataBag implements Subject {
private ArrayList list = new ArrayList();
private ArrayList observers = new ArrayList();
public void add( Integer i ) {
list.add( i );
notifyObservers();
}
public Iterator iterator() {
return list.iterator();
}
public Integer remove( int index ) {
if( index < list.size() ) {
Integer i = (Integer) list.remove( index );
notifyObservers();
return i;
}
return null;
}
public void addObserver( Observer o ) {
observers.add( o );
}
public void removeObserver( Observer o ) {
observers.remove( o );
}
private void notifyObservers() {
// loop through and notify each observer
Iterator i = observers.iterator();
while( i.hasNext() ) {
Observer o = ( Observer ) i.next();
o.update( this );
}
}
}
IntegerDataBag holds onto Integer instances. The IntegerDataBag also allows Observers to
add and remove themselves.
Consider these two implementations of Observer -- IntegerAdder and IntegerPrinter:
import java.util.Iterator;
public class IntegerAdder implements Observer {
private IntegerDataBag bag;
public IntegerAdder( IntegerDataBag bag ) {
this.bag = bag;
bag.addObserver( this );
}
public void update( Subject o ) {
if( o == bag ) {
System.out.println(
"The contents of the IntegerDataBag have changed." );
int counter = 0;
Iterator i = bag.iterator();
while( i.hasNext() ) {
Integer integer = ( Integer ) i.next();
counter+=integer.intValue();
}
System.out.println(
"The new sum of the integers is: " + counter );
}
}
}
import java.util.Iterator;
public class IntegerPrinter implements Observer {
private IntegerDataBag bag;
public IntegerPrinter( IntegerDataBag bag ) {
this.bag = bag;
bag.addObserver( this );
}
public void update( Subject o ) {
if( o == bag ) {
System.out.println(
"The contents of the IntegerDataBag have changed." );
System.out.println(
"The new contents of the IntegerDataBag contains:" );
Iterator i = bag.iterator();
while( i.hasNext() ) {
System.out.println( i.next() );
}
}
}
}
IntegerAdder and IntegerPrinter add themselves to the integer bag as observers. When an
IntegerAdder receives an update, it sums up the Integer values held in the bag and displays
them. Likewise, when IntegerPrinter receives an update, it prints out the Integers held in the
bag.
Here is a simple main() that exercises these classes:
public class Driver {
public static void main( String [] args ) {
Integer i1 = new Integer( 1 ); Integer i2 = new Integer( 2 );
Integer i3 = new Integer( 3 ); Integer i4 = new Integer( 4 );
Integer i5 = new Integer( 5 ); Integer i6 = new Integer( 6 );
Integer i7 = new Integer( 7 ); Integer i8 = new Integer( 8 );
Integer i9 = new Integer( 9 );
IntegerDataBag bag = new IntegerDataBag();
bag.add( i1 ); bag.add( i2 ); bag.add( i3 ); bag.add( i4 );
bag.add( i5 ); bag.add( i6 ); bag.add( i7 ); bag.add( i8 );
IntegerAdder adder = new IntegerAdder( bag );
IntegerPrinter printer = new IntegerPrinter( bag );
// adder and printer add themselves to the bag
System.out.println( "About to add another integer to the bag:" );
bag.add( i9 );
System.out.println("");
System.out.println("About to remove an integer from the bag:");
bag.remove( 0 );
}
}
Upon running the main, you will see: