General musings on programming languages, and Java.

Wednesday, April 28, 2010

Using types to help refactor

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);
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

1 comment:

khattak said...

a good example is in Hudson Environment Variables

http://github.com/kohsuke/hudson/blob/master/core/src/main/java/hudson/EnvVars.java#L224

Blog Archive

About Me

A salsa dancing, DJing programmer from Manchester, England.