Adapting Classes
A few days ago, the wisdom of the java.io.Reader interface dawned on me suddenly, and at the same moment the world of interfaces came into a new light. I’ve always understood what an interface (or pure virtual class) is, and the purpose of them - they allow you to change the implementation of your class without changing the calling code. Some people have even told me that the use of interfaces can replace multiple inheritance - but I never really got how.
For those of you who are unaware, Reader is a basic interface that reads arrays of character data from “some source”. This seems like a good idea, of course. You get the data, and you don’t care about the source. Yay, nice and simple, and everyone is happy. Typically one creates a java.io.FileInputStream, creates a java.io.InputStreamReader (which implements Reader), and you’re off to the races.
One day, another class caught my eye: java.io.BufferedReader. This class implements the Reader interface, but doesn’t specify in the name any kind of data source. How does this class work? A BufferedReader takes another Reader instance as part of its constructor, and adapts it.
Why is this such a special idea? Because BufferedReader is not derived from InputStreamReader. As a result, any Reader can be buffered by this simple class. In the same way, other classes can adapt a BufferedReader to add additional functionality. A LineNumberReader can take a BufferedReader, an InputStreamReader, a KeyboardJunkReader, a RandomDataReader, or a ManagementBullshitReader - whatever Reader one wants to count the lines of. (The fact that LineNumberReader is derived from BufferedReader is irrelevant [and frankly, pointless…])
So, you’re thinking “fine, but so what?”. Let me give you an example of another simple interface that this kind of adapting would be cool for:
public interface XYDataset
{
public int getCount();
public Number getX(int index);
public Number getY(int index);
}
This interface is pretty simple, and would be good as part of a plotting package. Any set of X and Y values could be plotted easily by creating an instance of this XYDataset wherever your data is. This is simple, effective, and cool. A basic implementation of this could have two List objects, or one List object, or … whatever, who cares - storing data is boring.
What if you find that some users are plotting thousands of points, and it’s very slow? Let’s create a filtering adapter class:
public class FilteringXYDataset implements XYDataset
{
private XYDataset delegate;
private int maxPoints = 1000; // only plot this many points.
public FilteringXYDataset(XYDataset initDelegate)
{
delegate = initDelegate;
}
public int getCount()
{
int realCount = delegate.getCount();
if (realCount < maxPoints)
return realCount;
else
return maxPoints;
}
public Number getX(int index)
{
int correctItemCount = delegate.getCount();
if (correctItemCount < numPts)
return delegate.getX(index);
int newIdx = (int)(correctItemCount * ((double)item / (double) maxPoints));
return delegate.getX(newIdx);
}
// (repeat for getY)
}
Cool, the dataset is filtered now. It’s a crude filtering, but when you’re plotting a thousand points it’ll do nicely - it’s hard to tell a thousand points from ten thousand points on a normal screen sized plot.
What other dataset adapters could you use?
- Add an extra 50% to the number of points, and generate them from a bezier smoothing curve.
- Add a (0, 0) point to every dataset. Pretend the count is one greater, and then add the point in at the appropriate index.
- Plotting arbitrary X-Y points on a log-log plot is impossible if the points are negative - so chop them out in another adapter class.
Adapting classes like this are simple and nifty - you take one interface, and provide the same interface back to the library user.
The really great part is that, without multiple inheritence, you can later create a dataset that is smoothed, filtered, and has negative points chopped out - all with one line of code. The smoothing and filtering algorithms are only written once, but can be applied in various orders and with various other tools. … and you still don’t know how the XYDataset is being stored. Good!