Mathieu Fenniak's Weblog

2004/11/23

Adapting Classes

Filed under: java, programming — admin @ 12:55 am

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!

No Comments

No comments yet.

RSS feed for comments on this post. TrackBack URL

Sorry, the comment form is closed at this time.

Powered by WordPress