In some real code, I changed an int that was used to index into an array of ViewerPanels, so that the ViewerPanel was passed around instead of the int. I missed some cases in the first pass, so I had a think about how to make sure I don't do that again.
The cases I missed were PropertyChangeListeners. If you're not familiar with that type, you can use it with a PropertyChangeSupport to listen for changes in values. It has Object in its API, rather than using generics. I don't particularly like the idea of listening for changes, particularly if the listeners then end up interfering with the objects they're listening to, but that's what I have in existing code, so I need to get on with it for now.
The tests didn't pick it up, I found it by coming across a firePropertyChange and wondering what the listening end now looked like. The tests could be better, sure.
Here follows four versions of a simpler code sample. The refactor we want to do in this case is just to go from double to float. Version 2 shows what happens when one forgets to check the PropertyChangeListeners. Version 3 steps back to Version 1 but alters it to use a type-safe version of PropertyChangeListener. Version 4 does the refactor again, but this time the compiler forces the ChangeListener to be altered, because we're no longer based on PropertyChangeListener.
The below code is executable.
import java.beans.PropertyChangeEvent; | |
import java.beans.PropertyChangeListener; | |
import java.beans.PropertyChangeSupport; | |
import java.util.ArrayList; | |
import java.util.List; | |
class Main { | |
public static void main(String[] args) { | |
System.out.println("Version 1"); | |
MutableComplex.main(); | |
System.out.println("Version 2, changing double to float but not changing the PropertyChangeListener"); | |
try { | |
MutableComplex2.main(); | |
} | |
catch (ClassCastException e) { | |
e.printStackTrace(); | |
} | |
System.out.println("Version 3, based on version 1 but dumping the PCListener in favour of types."); | |
MutableComplex3.main(); | |
System.out.println("Version 4, based on version 3 but with the refactor from double to float."); | |
MutableComplex4.main(); | |
} | |
} | |
class MutableComplex { | |
private double re, im; | |
public double re() { return re; } | |
public double im() { return im; } | |
private final PropertyChangeSupport support = new PropertyChangeSupport(this); | |
public static final String RE = "Re"; | |
public static final String IM = "Im"; | |
public void addPropertyChangeListener(PropertyChangeListener listener) { | |
support.addPropertyChangeListener(listener); | |
} | |
public void re(double re) { | |
support.firePropertyChange(RE, this.re, this.re = re); | |
} | |
public void im(double im) { | |
support.firePropertyChange(IM, this.im, this.im = im); | |
} | |
public static void main(String... args) { | |
MutableComplex complex = new MutableComplex(); | |
complex.addPropertyChangeListener(new PropertyChangeListener() { | |
@Override | |
public void propertyChange(PropertyChangeEvent event) { | |
if (event.getPropertyName().equals(RE)) | |
System.out.println(RE + " was changed by " + | |
((Double)event.getNewValue() - (Double)event.getOldValue()) + " units."); | |
} | |
}); | |
complex.re(3); | |
complex.im(4); | |
complex.re(5); | |
} | |
} | |
class MutableComplex2 { | |
private float re, im; | |
public float re() { return re; } | |
public float im() { return im; } | |
private final PropertyChangeSupport support = new PropertyChangeSupport(this); | |
public static final String RE = "Re"; | |
public static final String IM = "Im"; | |
public void addPropertyChangeListener(PropertyChangeListener listener) { | |
support.addPropertyChangeListener(listener); | |
} | |
public void re(float re) { | |
support.firePropertyChange(RE, this.re, this.re = re); | |
} | |
public void im(float im) { | |
support.firePropertyChange(IM, this.im, this.im = im); | |
} | |
public static void main(String... args) { | |
MutableComplex2 complex = new MutableComplex2(); | |
complex.addPropertyChangeListener(new PropertyChangeListener() { | |
@Override | |
public void propertyChange(PropertyChangeEvent event) { | |
if (event.getPropertyName().equals(RE)) | |
System.out.println(RE + " was changed by " + | |
((Double)event.getNewValue() - (Double)event.getOldValue()) + " units."); | |
} | |
}); | |
complex.re(3); | |
complex.im(4); | |
complex.re(5); | |
} | |
} | |
interface ChangeListener<T> { | |
void changed(T oldValue, T newValue); | |
} | |
final class Watch<T> { | |
private final List<ChangeListener<T>> listeners = new ArrayList<ChangeListener<T>>(); | |
public void addListener(ChangeListener<T> listener) { | |
listeners.add(listener); | |
} | |
public void changed(T oldValue, T newValue) { | |
for (ChangeListener<T> listener: listeners) | |
listener.changed(oldValue, newValue); | |
} | |
} | |
class MutableComplex3 { | |
private double re, im; | |
public double re() { return re; } | |
public double im() { return im; } | |
public final Watch<Double> watchRe = new Watch<Double>(); | |
public final Watch<Double> watchIm = new Watch<Double>(); | |
public void re(double re) { | |
watchRe.changed(this.re, this.re = re); | |
} | |
public void im(double im) { | |
watchIm.changed(this.im, this.im = im); | |
} | |
public static void main(String... args) { | |
MutableComplex3 complex = new MutableComplex3(); | |
complex.watchRe.addListener(new ChangeListener<Double>() { | |
@Override | |
public void changed(Double oldValue, Double newValue) { | |
System.out.println("Re was changed by " + (newValue - oldValue) + " units."); | |
} | |
}); | |
complex.re(3); | |
complex.im(4); | |
complex.re(5); | |
} | |
} | |
class MutableComplex4 { | |
private float re, im; | |
public float re() { return re; } | |
public float im() { return im; } | |
public final Watch<Float> watchRe = new Watch<Float>(); | |
public final Watch<Float> watchIm = new Watch<Float>(); | |
public void re(float re) { | |
watchRe.changed(this.re, this.re = re); | |
} | |
public void im(float im) { | |
watchIm.changed(this.im, this.im = im); | |
} | |
public static void main(String... args) { | |
MutableComplex4 complex = new MutableComplex4(); | |
complex.watchRe.addListener(new ChangeListener<Float>() { | |
@Override | |
public void changed(Float oldValue, Float newValue) { | |
System.out.println("Re was changed by " + (newValue - oldValue) + " units."); | |
} | |
}); | |
complex.re(3); | |
complex.im(4); | |
complex.re(5); | |
} | |
} |
1 comment:
a good example is in Hudson Environment Variables
http://github.com/kohsuke/hudson/blob/master/core/src/main/java/hudson/EnvVars.java#L224
Post a Comment