• Tidak ada hasil yang ditemukan

Subject-Observer

Dalam dokumen Book Java Generics and Collections (Halaman 154-161)

Design Patterns

9.5 Subject-Observer

return Math.round(payer.getIncome() * RATE);

} }

class DodgingTaxStrategy<P extends TaxPayer<P>> implements TaxStrategy<P> { public long computeTax(P payer) { return 0; }

}

class TrustTaxStrategy extends DefaultTaxStrategy<Trust> { public long computeTax(Trust trust) {

return trust.isNonprofit() ? 0 : super.computeTax(trust);

} }

Now an instance of NonProfitTrust takes a strategy that expects a NonProfitTrust as an argument, and ForProfitTrust behaves similarly. It is often convenient to set up a parameterized type hierarchy in this way, where classes with subclasses take a type parameter and are abstract and classes without subclasses do not take a type parameter and are final. A body for the getThis method is declared in each final subclass.

Summary As we have seen, recursive type parameters often appear in Java:

class TaxPayer<P extends TaxPayer<P>>

Comparable<T extends Comparable<T>>

class Enum<E extends Enum<E>>

The getThis trick is useful in this situation whenever one wants to use this in the base type with the more specific type provided by the type parameter.

We will see another example of recursive type parameters in the next section, applied to two mutually recursive classes. However, although the getThis trick is often useful, we will not require it there.

The appearance of Object in a method signature often indicates an opportunity to gen- erify. So we should expect to generify the classes by adding a type parameter, A, corre- sponding to the argument type. Further, we can replace Observable and Observer them- selves 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 Observa ble class and the Observer interface. Here are the relevant headers:

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 recursion—for 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.

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() {...}

}

9.5 Subject-Observer | 137

package java.util;

public interface Observer {

public void update(Observable o, Object arg);

}

Example 9-9. 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);

}

As a demonstration client for Observable and Observer, a currency converter is presen- ted in Example 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.

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 instan- tiated 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 di- rectory 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 javax—for 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 Example 9-10. Currency converter import java.util.*;

import javax.swing.*;

Figure 9-1. Currency converter

9.5 Subject-Observer | 139

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) {

9.5 Subject-Observer | 141

new Converter().setVisible(true);

} }

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 -Xbootclass path 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.

PART II

Dalam dokumen Book Java Generics and Collections (Halaman 154-161)