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
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: