The Observer interface is useful in non-graphical applications, too. It allows you to
create more modular software by reducing the direct interconnection of components. The Observer interface is sort of a one-way interaction. The Observers usually know
what kind of object they're watching, but the Observables usually just know that there are Observers watching.
Caution
When you use the Observer interface, you gain a lot of flexibility, because objects no longer have to know what objects are watching them. An observed object just sends out a notification that it has changed. You now have a lot of objects that are acting completely on their own, responding to updates and performing their own tasks. You must pay close attention to your object model here. Make sure each object is only performing the tasks it is responsible for. Try to make each object's roles and responsibilities as clear as possible. Otherwise, debugging a system like this will be a nightmare.
Listing 9.18 shows an example Observable class that represents an aircraft for a flight tracking system. Notice that none of its instance variables are public. You need to be able to notice when a variable changes value. If your instance variables are all public, any object can come along and change the value without you being notified. By restricting all the variable manipulation to accessor functions (get/set functions), you maintain the ability to notice when a variable changes.
Tip
When you call notifyObservers, you may pass it an object that will be passed to the update method in all observers. You can use this object to pass specific information about the update. If you plan to handle multiple types of updates, you should create an event class, similar to the AWT's Event class, which tells the update method what kind of update it is and which objects are involved.
Listing 9.18 Source Code for Aircraft.java
import java.util.*;
// This class demonstrates an observable object with multiple // values that can change. If an individual value is changed, // it calls notifyObservers. You can also change the values in // bulk using setAll.
//
// When you create observable classes like this, you can't have // public variables if you need to know when those varables change. // Otherwise, if altitude was public, anyone could say:
// aircraft.altitude = 10000;
Chapter 9 -- Creating Reusable Graphics Components
// and no observers would be notified. public class Aircraft extends Observable {
protected String id;
protected double latitude; protected double longitude; protected double altitude; protected double speed;
public Aircraft(String id) {
this.id = id; }
public Aircraft(String id, double lat, double lon, double alt, double speed) { this.id = id; this.latitude = lat; this.longitude = lon; this.altitude = alt; this.speed = speed; }
public String getID() { return id; }
public double getAltitude() { return altitude; } public void setAltitude(double newAlt)
{
altitude = newAlt; setChanged();
notifyObservers(this); }
public double getLatitude() { return latitude; } public void setLatitude(double newLat)
{
latitude = newLat; setChanged();
notifyObservers(this); }
public double getLongitude() { return longitude; } public void setLongitude(double newLon)
{
longitude = newLon; setChanged();
Chapter 9 -- Creating Reusable Graphics Components
}
public double getSpeed() { return speed; } public void setSpeed(double newSpeed)
{
speed = newSpeed; setChanged();
notifyObservers(this); }
public void setAll(double lat, double lon, double alt, double speed) { this.latitude = lat; this.longitude = lon; this.altitude = alt; this.speed = speed; setChanged(); notifyObservers(this); } }
Listing 9.19 shows an example module that watches an Aircraft for changes and prints a warning when the altitude is too high.
Listing 9.19 Source Code for AltitudeMonitor.java
import java.util.*;
// This class demonstrates how you can add new features to an // application without rewriting a lot of code. In this case, // this is a module that monitors aircraft altitudes and prints // out a warning if one gets too high. The aircraft class doesn't // know anything about this class, their only interaction is
// through the Observer-Observable interface.
public class AltitudeMonitor extends Object implements Observer {
double maxAltitude;
public AltitudeMonitor(double maxAlt) {
this.maxAltitude = maxAlt; }
// Somewhere in your application you will have to add code
Chapter 9 -- Creating Reusable Graphics Components
// to tell this object about new aircraft.
public void addAircraft(Aircraft newAircraft) {
newAircraft.addObserver(this); }
public void update(Observable obs, Object arg) {
// Make sure this update is for an aircraft
if (!(obs instanceof Aircraft)) return; Aircraft ac = (Aircraft) obs;
if (ac.getAltitude() > maxAltitude) {
System.out.println("Warning! Aircraft too high!"); return;
} }
}
Figure 9.7 shows the relationship between an altitude monitor and an aircraft.
Figure 9.7 : The altitude monitor registers itself as an observer of an aircraft and then watches the aircraft's altitude.
One of the advantages of designing things this way is that AltitudeMonitor could be an add-on feature to your tracking system. You could create whole sets of monitors similar to this that your customer could pick from. Notice, however, there's still one little flaw here. When you add a new aircraft, you have to call addAircraft in the
AircraftMonitor object. If you wanted to add new types of monitors, you'd have to call a similar method in the new
monitor. This is not good. You want to be able to add a new monitor without adding even one line of code. You can do it, too!
You can create an AircraftRegistry class that is an Observable. Its job in life is to notify its observers whenever a new aircraft is added. Instead of calling addAircraft for each different monitor you have in your system, you just call
addAircraft in the registry, and it notifies its observers of the new aircraft. Listing 9.20 shows an implementation of an
AircraftRegistry class. It is implemented as a singleton class, which means there is only one in the entire
system. A singleton class is implemented by keeping a protected static pointer to the lone instance of the class. You also hide the constructor so no one can create their own instance. Then, you create a static method that returns the lone instance of the class, creating a new one if there wasn't one already.
Listing 9.20 Source Code for AircraftRegistry.java
import java.util.*;
Chapter 9 -- Creating Reusable Graphics Components
// about new aircraft. It is implemented as a singleton class, which // means there is only one. Its constructor is protected, so you // can't create a new AircraftRegistry manually. Any time you need // the registry, you access it through: AircraftRegistry.instance() public class AircraftRegistry extends Observable
{
// reference to the single instance of AircraftRegistry in the system protected static AircraftRegistry registry;
protected AircraftRegistry() {
}
// Return the lone instance of this class. If there isn't one, create it. public synchronized static AircraftRegistry instance()
{
if (registry == null) {
registry = new AircraftRegistry(); }
return registry; }
// When an aircraft is added to the system, notify all the interested parties public void addAircraft(Aircraft aircraft)
{
setChanged();
// Pass the new aircraft to the interested parties notifyObservers(aircraft);
} }
Now, the AltitudeMonitor class no longer needs the AddAircraft method. Instead, its update method has to be smart enough to know whether the update came from an Aircraft or from the AircraftRegistry. Listing 9.21 shows the updated AltitudeMonitor class.
Listing 9.21 Source Code for AltitudeMonitor2.java
import java.util.*;
// This class demonstrates how you can add new features to an // application without rewriting a lot of code. In this case,
Chapter 9 -- Creating Reusable Graphics Components
// this is a module that monitors aircraft altitudes and prints // out a warning if one gets too high. The aircraft class doesn't // know anything about this class, their only interaction is
// through the Observer-Observable interface. //
// This class uses the AircraftRegistry to learn about new aircraft. public class AltitudeMonitor2 extends Object implements Observer { double maxAltitude; public AltitudeMonitor2() { maxAltitude = 40000.0; AircraftRegistry.instance().addObserver(this); }
public AltitudeMonitor2(double maxAlt) {
this.maxAltitude = maxAlt;
AircraftRegistry.instance().addObserver(this); }
public void update(Observable obs, Object arg) {
// See if this update is for an aircraft if (obs instanceof Aircraft) { Aircraft ac = (Aircraft) obs;
if (ac.getAltitude() > maxAltitude) { System.out.println(
"Warning! Aircraft too high!"); return;
}
// If this update is from the registry, it is telling us about // a new aircraft, so start observing the new aircraft
} else if (obs instanceof AircraftRegistry) { Aircraft ac = (Aircraft) arg;
ac.addObserver(this); }
} }
This may seem like a lot of fuss to you, but it makes your software much more modular. The AircraftRegistry class provides just the extra level of abstraction to really make this system modular. Now you can add new monitors without
Chapter 9 -- Creating Reusable Graphics Components
changing a line of code anywhere in the program. You can dynamically load new monitors on-the-fly, thanks to Java's class loading interface.
Figure 9.8 shows the relationship between an aircraft, the aircraft registry, and the altitude monitor.
Figure 9.8 : The aircraft registry sends out updates when a new aircraft is created.
Listing 9.22 shows a simple program that tests the interaction between an Aircraft, the AircraftRegistry, and
the AltitudeMonitor classes. The monitors array contains a list of the aircraft monitors to be loaded, currently just
the AltitudeMonitor2 class. This list could be read in from a file just as easily, so you wouldn't have to recompile
even the test program to add new monitors; however, for this demonstration, a static array is sufficient.
Listing 9.22 Source Code for TestMonitor.java
// This class demonstrates the highly dynamic nature of the // Aircraft, AircraftRegistry, and AltitudeMonitor classes. public class TestMonitor extends Object
{
// The list of monitors to dynamically load.
static String monitors[] = { "AltitudeMonitor2" }; // Load the monitors dynamically
public static void createMonitors() {
for (int i=0; i < monitors.length; i++) { try {
// Use the class loader. If the load fails, print an error message, but // keep running.
Class monClass = Class.forName(monitors[i]); monClass.newInstance();
} catch (Exception e) {
System.err.println("Got error creating class "+ monitors[i]);
System.err.println(e); }
} }
public static void main(String[] args) {
// Dynamically load the aircraft monitors createMonitors();
// Create a dummy aircraft
Chapter 9 -- Creating Reusable Graphics Components
Aircraft ac = new Aircraft("MW1234NA", 0.0, 0.0, 10000.0, 400.0);
// Add the dummy aircraft to the system
AircraftRegistry.instance().addAircraft(ac);
// Play with the altitudes and see if the monitor catches it. System.out.println("Setting to 12000"); ac.setAltitude(12000.0); System.out.println("Setting to 48000"); ac.setAltitude(48000.0); } }
The only disadvantage of this program dynamically loading the monitors is that the dynamic loading process can only call the empty constructor for the monitor. You would have to find alternate means of the monitors getting their configuration. Although these examples are fairly specific to a particular application, the concepts apply to a wide range of applications. Use the Observer-Observable interface to separate components as much as possible. You will find that it is much easier to plug in new components.
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-1.gif
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-3.gif
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-5.gif
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/f9-7.gif
file:///E|/Java%20Professor/Hacking%20Java%20Professional%20Resource%20Kit/ch10.htm