[Biojava-l] Changing Changeable
Matthew Pocock
matthew_pocock@yahoo.co.uk
Sat, 27 Apr 2002 21:50:44 +0100
Hi all,
There is a lot of repetative and error-prone coding required to
implement a changeable class. This tends to involve either inheriting
from AbstractChangeable, or delegating to ChangeSupport (something that
has to be done a bit carefly to avoid synchronization problems if lazily
instantiating the ChangeSupport delegate). Implementing the code in
ChangeSupport for managing listeners for each case is possible, but
realy would open the codebase up to a plethora of nasties. Each method
that alters a changeable property has to put in some realy boring and
repetative but critical code involving a check for listeners and a
synchronized block.
I propose that we add an interface PropertyChanger and a method
ChangeSupport.fireProperty which uses a PropertyChanger and a
ChangeEvent to update the value of the field, while taking care of
synchronization and event notification. AbstractChangeable has an
apropreately modified signature. Here are the changes (with a bit of
pseudo-code where I can't be bothered to write the whole mess out).
public interface PropertyChanger {
// update the property
public void change(ChangeEvent ce);
// roll back the property
// - not sure if we want this yet - left out of my initial attempt
public void unchange(ChangeEvent ce);
}
public class ChangeSupport {
// drop firePreChange and firePostChange
...
public void fireChange(PropertyChanger pc, ChangeEvent ce)
throws ChangeVetoException {
foreach(listener)
listener.preChange(ce);
pc.change(ce);
foreach(listener)
listener.postChange(ce);
}
...
}
ChangeSupport will allow multiple threads to fire events symultaneously
and will make sure that the listener list is maintained in a safe
manner. No external synchronization on ChangeSupport will be needed any
more, removing many opportunities for bugs.
Here is an example of using a PropertyChanger that captures state in a
closure. The old method looks like this:
public void train(DistributionTrainerContext dtc, double weight)
throws ChangeVetoException {
if(!hasListeners()) {
trainImpl(dtc, weight);
} else {
ChangeSupport changeSupport = getChangeSupport(Distribution.WEIGHTS);
synchronized(changeSupport) {
ChangeEvent ce = new ChangeEvent(
SimpleDistribution.this,
Distribution.WEIGHTS
);
changeSupport.firePreChangeEvent(ce);
trainImpl(dtc, weight);
changeSupport.firePostChangeEvent(ce);
}
}
}
becomes:
public void train(final DistributionTrainerContext dtc, final double weight)
throws ChangeVetoException {
fireChange(
new PropertyChanger() {
public void change(ChangeEvent ce) {
trainImpl(dtc, weight);
}
}
new ChangeEvent(
SimpleDistribution.this,
Distribution.WEIGHTS
)
);
}
Admittedly this doesn't cut down the number of lines of code drasticaly,
but it does greatly reduce the complexity of the code.
Here is another example taken from SimpleEmissionState. The old code is:
public final void setDistribution(Distribution dis)
throws ChangeVetoException {
if(!hasListeners()) {
this.dis = dis;
} else {
ChangeEvent ce = new ChangeEvent(
this, EmissionState.DISTRIBUTION,
// actualy a bug - these two are the wrong way arround
this.dis, dis
);
ChangeSupport changeSupport =
getChangeSupport(EmissionState.DISTRIBUTION);
synchronized(changeSupport) {
changeSupport.firePreChangeEvent(ce);
this.dis = dis;
changeSupport.firePostChangeEvent(ce);
}
}
}
This becomes:
private static final PropertyChanger DISTRIBUTION_CHANGER
= new PropertyChanger() {
public void change(ChangeEvent ce) {
SimpleEmissionState ses = (SimpleEmissionState) ce.getSource();
ses.dis = (Distribution) ce.getChange();
}
};
public final void setDistribution(Distribution dis)
throws ChangeVetoException {
fireChange(
DISTRIBUTION_CHANGER,
ChangeEvent ce = new ChangeEvent(
this, EmissionState.DISTRIBUTION,
dis, this.dis
)
);
}
Obviously, there is no real need to make the changer into a static
field, but doing it this way feels nice to me, and cuts down on
object-churn. All the information about what should change is
encapsulated by the PropertyChanger instance and the ChangeEvent.
This allows us to communicate changes to properties arround the system.
The ChangeSupport object knows all about the registered listeners and
the change types that should never be thrown (according to the
unChanging method). By decoupling the update code from the event fireing
stuff we can potentialy batch updates. E.g. when committing a set of
sequences to a database, we can fire all the preChange notifications,
then do all the updates and finaly fire all the postChange notifications.
If we were to add the unchange method to PropertyChanger, we can
potentialy roll-back things when they go wrong. In the database example,
if any one of the fifty sequences being added failed we could make sure
that the ones added so far are removed, leaving the seqDB in the state
it was before. None of this can be done with the current system.
This realy would move us a few steps towards much more robust handling
of data, and allows the vast majority of the overhead to be optimized
away for common cases (no listeners, one thread only). It becomes much
easier to write changeable classes. The main cost is that a large number
of files must be touched. All of these will need to be tested of course.
Perhaps the JUnit coverage will be increased.
As always, nothing is set in stone, and all comments are welcome. In
particular, if you have a use-case for the events framework that is not
realy covered at the moment, now is the time to say.
Matthew