Section 9.5. Subject-Observer


9.5. Subject-Observer

We finish with a more extended example, illustrating the generic Subject-Observer pattern. Like the Strategy pattern, the Subject-Observer pattern uses parallel class hierarchies, but this time we require two type variables with mutually recursive bounds, one to stand for the specific kind of subject and one to stand for the specific kind of observer. This is our first example of type variables with mutually recursive bounds.

The Java library implements a nongeneric version of the Subject-Observer pattern in the package java.util with the class Observable and the interface Observer (the former corresponding to the subject), signatures for which are shown in Example 9.8.

Observable and Observer before generics

 package java.util; public class Observable {   public void addObserver(Observer o) {...}   protected void clearChanged() {...}   public int countObservers() {...}   public void deleteObserver(Observer o) {...}   public boolean hasChanged() {...}   public void notifyObservers() {...}   public void notifyObservers(Object arg) {...}   protected void setChanged() {...} } package java.util; public interface Observer {     public void update(Observable o, Object arg); } 

The Observable class contains methods to register observers (addObserver), to indicate that the observable has changed (setChanged), and to notify all observers of any changes (notifyObservers), among others. The notifyObservers method may accept an arbitrary argument of type Object that is to be broadcast to all the observers. The Observer interface specifies the update method that is called by notifyObservers. This method takes two parameters: the first, of type Observable, is the subject that has changed; the second, of type Object, is the broadcast argument.

The appearance of Object in a method signature often indicates an opportunity to generify. So we should expect to generify the classes by adding a type parameter, A, corresponding to the argument type. Further, we can replace Observable and Observer themselves with the type parameters S and O (for Subject and Observer). Then within the update method of the observer, you may call on any method supported by the subject S without first requiring a cast.

Example 9.9 shows how to specify corresponding generic signatures for the Observable class and the Observer interface. Here are the relevant headers:

Observable and Observer with generics

 package java.util; class StubException extends UnsupportedOperationException {} public class Observable<S extends Observable<S,O,A>,                         O extends Observer<S,O,A>,                         A> {   public void addObserver(O o)     { throw new StubException(); }   protected void clearChanged()    { throw new StubException(); }   public int countObservers()      { throw new StubException(); }   public void deleteObserver(O o)  { throw new StubException(); }   public boolean hasChanged()      { throw new StubException(); }   public void notifyObservers()    { throw new StubException(); }   public void notifyObservers(A a) { throw new StubException(); }   protected void setChanged()      { throw new StubException(); } } package java.util; public interface Observer<S extends Observable<S,O,A>,                           O extends Observer<S,O,A>,                           A> {     public void update(S o, A a); } 

 public class Observable<S extends Observable<S,O,A>,                         O extends Observer<S,O,A>,                        A> public interface Observer<S extends Observable<S,O,A>,                           O extends Observer<S,O,A>,                           A> 

Both declarations take the same three type parameters. The declarations are interesting in that they illustrate that the scope of type parameters can be mutually recursive: all three type parameters appear in the bounds of the first two. Previously, we saw other examples of simple recursionfor instance, in the declarations of Comparable and Enum, and in the previous section on the Strategy pattern. But this is the first time we have seen mutual recursion.

Examining the bodies of the declarations, you can see that O but not S appears in the body of the Observable class and that S but not O appears in the body of the Observer interface. So you might wonder: could the declarations be simplified by dropping the type parameter S from Observable and the type parameter O from Observer? But this won't work, since you need S to be in scope in Observable so that it can be passed as a parameter to Observer, and you needs O to be in scope in Observer so that it can be passed as a parameter to Observable.

The generic declarations use stubs, as explained in Section 5.4.2. We compile the client against the generic signatures of Observable and Observer, but run the code against the class files in the standard Java distribution. We use stubs because we don't want to make any changes to the source of the library, since it is maintained by Sun.

As a demonstration client for Observable and Observer, a currency converter is presented in Examples 9.10. A screenshot of the converter is shown in Figure 9.1. The converter allows you to enter conversion rates for each of three currencies (dollars, euros, and pounds), and to enter a value under any currency. Changing the entry for a rate causes the corresponding value to be recomputed; changing the entry for a value causes all the values to be recomputed.

Figure 9-1. Currency converter


Currency converter

 import java.util.*; import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; enum Currency { DOLLAR, EURO, POUND } class CModel extends Observable<CModel,CView,Currency> {   private final EnumMap<Currency,Double> rates;   private long value = 0;  // cents, euro cents, or pence   private Currency currency = Currency.DOLLAR;   public CModel() {     rates = new EnumMap<Currency,Double>(Currency.class);   }   public void initialize(double... initialRates) {     for (int i=0; i<initialRates.length; i++)       setRate(Currency.values()[i], initialRates[i]);   }   public void setRate(Currency currency, double rate) {     rates.put(currency, rate);     setChanged();     notifyObservers(currency);   }   public void setValue(Currency currency, long value) {     this.currency = currency;     this.value = value;     setChanged();     notifyObservers(null);   }   public double getRate(Currency currency) {     return rates.get(currency);   }   public long getValue(Currency currency) {     if (currency == this.currency)       return value;     else       return Math.round(value * getRate(currency) / getRate(this.currency));   } } interface CView extends Observer<CModel,CView,Currency> {} class RateView extends JTextField implements CView {   private final CModel model;   private final Currency currency;   public RateView(final CModel model, final Currency currency) {     this.model = model;     this.currency = currency;     addActionListener(new ActionListener() {         public void actionPerformed(ActionEvent e) {           try {             double rate = Double.parseDouble(getText());             model.setRate(currency, rate);           } catch (NumberFormatException x) {}         }       });     model.addObserver(this);   }   public void update(CModel model, Currency currency) {     if (this.currency == currency) {       double rate = model.getRate(currency);       setText(String.format("%10.6f", rate));     }   } } class ValueView extends JTextField implements CView {   private final CModel model;   private final Currency currency;   public ValueView(final CModel model, final Currency currency) {     this.model = model;     this.currency = currency;     addActionListener(new ActionListener() {         public void actionPerformed(ActionEvent e) {           try {             long value = Math.round(100.0*Double.parseDouble(getText()));             model.setValue(currency, value);           } catch (NumberFormatException x) {}         }       });     model.addObserver(this);   }   public void update(CModel model, Currency currency) {     if (currency == null || currency == this.currency) {       long value = model.getValue(this.currency);       setText(String.format("%15d.%02d", value/100, value%100));     }   } } class Converter extends JFrame {   public Converter() {     CModel model = new CModel();     setTitle("Currency converter");     setLayout(new GridLayout(Currency.values().length+1, 3));     add(new JLabel("currency"));     add(new JLabel("rate"));     add(new JLabel("value"));     for (Currency currency : Currency.values()) {       add(new JLabel(currency.name()));       add(new RateView(model, currency));       add(new ValueView(model, currency));     }     model.initialize(1.0, 0.83, 0.56);     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     pack();   }   public static void main(String[] args) {     new Converter().setVisible(true);   } } 

The client instantiates the pattern by declaring CModel to be a subclass of Observable, and CView to be a subinterface of Observer. Furthermore, the argument type is instantiated to Currency, an enumerated type, which can be used to inform an observer which components of the subject have changed. Here are the relevant headers:

 enum Currency { DOLLAR, EURO, POUND } class CModel extends Observable<CModel, CView, Currency> interface CView extends Observer<CModel, CView, Currency> 

The classes RateView and ValueView implement CView, and the class Converter defines the top-level frame which controls the display.

The CModel class has a method to set and get the rate and value for a given currency. Rates are stored in a map that assigns a rate to each currency, and the value is stored (as a long, in cents, euro cents, or pence) together with its actual currency. To compute the value for a given currency, the value is divided by the rate for its actual currency and multiplied by the rate for the given currency.

The CModel class invokes the update method of RateView whenever a rate is changed, passing the corresponding currency as the argument (because only the rate and value for that currency need to be updated); and it invokes the update method of ValueView whenever a value is changed, passing null as the argument (because the values for all currencies need to be updated).

We compile and run the code as follows. First, we compile the generic versions of Observable and Observer:

 % javac -d . java/util/Observable.java java/util/Observer.java 

Since these are in the package java.util, they must be kept in the subdirectory java/util of the current directory. Second, we compile Converter and related classes in package com.eg.converter. By default, the Java compiler first searches the current directory for class files (even for the standard library). So the compiler uses the stub class files generated for Observable and Observer, which have the correct generic signature (but no runnable code):

 % javac -d . com/eg/converter/Converter.java 

Third, we run the class file for the converter. By default, the java runtime does not first search the current directory for class files in the packages java and javaxfor reasons of security, these are always taken from the standard library. So the runtime uses the standard class files for Observable and Observer, which contain the legacy code we want to run (but do not have the correct generic signature):

 % java com.eg.converter.Converter 

So when we use stubs for standard library classes, we do not need to alter the classpath, as we did in Section 5.4.2, because the correct behavior is obtained by default. (If you do want to alter the standard library classes at runtime, you can use the -Xbootclasspath flag.)

This concludes our discussion of generics. You now have a thorough grounding that enables you to use generic libraries defined by others, to define your own libraries, to evolve legacy code to generic code, to understand restrictions on generics and avoid the pitfalls, to use checking and specialization where needed, and to exploit generics in design patterns.

One of the most important uses of generics is the Collection Framework, and in the next part of this book we will show you how to effectively use this framework and improve your productivity as a Java programmer.




Java Generics and Collections
Java Generics and Collections
ISBN: 0596527756
EAN: 2147483647
Year: 2006
Pages: 136

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net