Properly Abstracted List APIs (in C#)

We frequently encounter APIs that take lists of objects as parameters. The code behind the API will likely want to know something about those objects. It might sort them or extract some metadata and then process them. It may return a list of the objects with additional data. The question at hand is: how should the API get the data it needs about those objects?

Presented below is an elegant solution which preserves separation of concerns.

In considering this problem, we’ll use a simple (read: contrived) example: a chart drawing API that needs an X and Y data point for each item in the chart. The client has a number of StockQuote objects it wishes to plot, defined as follows:

class StockQuote
{
    public float price;
    public DateTime date;
}

Suppose further that later on I want to be able to react to user interaction with the chart and obtain the relevant StockQuote object.

Simple Approach: Data Objects
A typical approach would be to define a data structure with the information in question. For example:

library:

class ChartPoint
{
    int objectId;
    public int x, y;
}

Chart CreateChart(List<ChartPoint> points);

client:

List<ChartPoint> points = new List<ChartPoint>();
foreach(StockQuote quote in quotes)
{
    points.add(new ChartPoint(quotes.IndexOf(quote),
        quote.date.DayOfYear,
        quote.price));
}

CreateChart(points);

This works, and is explicit, but is very wordy. It’s very easy to implement on the library side, of course. But I’ve got to create an intermediate list of this other type on the client side, which adds three lines of additional code on top of the call, plus an intermediate list. It also imposes this requirement that I have an Id to refer to my object, so I can know which one is which when later interacting with the chart. That’ll be another layer of lookup – this doesn’t feel right at all.

OO-Approach: Interfaces
Many would suggest the following method:

library:

interface IChartable
{
    public int X { get; }
    public int Y { get; }
}
Chart CreateChart(List<IChartable> points);

client:

class StockQuote : IChartable
{
    public float price;
    public DateTime date;

    public int X { get { ... } }
    public int Y { get { ... } }
}

ChartAPI.CreateChart(stockQuotes);

The actual invocation has improved, and I no longer need the Id field, since I’m passing in my objects directly. But now my class has to implement this ‘chartable’ interface that none of the rest of the code actually cares about. That doesn’t feel right either. We should be able to do better.

Generic Approach: Accessor Delegates
The question to ask is: what is the fundamental thing the library actually cares about? What is the core of the problem? The answer in this case is: for each object, what is the X value and what is the Y value? Or to phrase it differently: how I extract the values from the object in question? Lets provide that information directly.

library:

delegate int CoordinateAccessDelegate(object o);
Chart CreateChart(List<object> items,
    CoordinateAccessDelegate xAccess,
    CoordinateAccessDelegate yAccess);

client:

ChartAPI.CreateChart(quotes,
    delegate(StockQuote q) { return q.date.DayOfYear; },
    delegate(StockQuote q) { return q.date.price; } );

Since what we want is the way to access the X and Y data, we’ll give it exactly that: an access delegate. In this way, the chart library doesn’t particularly care about the format of the data I’m using, nor does the rest of my application care about the requirements of the chart library. And I avoid the painful copy step of the data-centric version.

A hidden benefit of this approach is revealed later, when the chart library needs to return to me information about some user interaction. It can do this now in my own terms, with my own objects, rather than by using some structure with an id field that I have to map to my object.

Some will have correctly observed by now that this is the same technique used to provide a sort comparator. This is exactly right! Understanding the technique and realizing it may be used elsewhere is the entire point.

Of course, this technique is not appropriate for all problems. If a large number of accessors are required, it probably shouldn’t be used. The goal is a literate API, one in which the invocation parameters represent the data that is actually required.

Technorati Tags: , <a

About these ads

2 thoughts on “Properly Abstracted List APIs (in C#)

  1. good post!

    your remark about a a large number of accessors is correct. it doesn’t feel right to pass 10 accessor delegates parameters. But you can fix it by combining this approach with .. interfaces.
    (I’m not a c# expert so here is a java equivalent)
    library:

    interface ChartInfo {
    int getX(T o);
    int getY(T o);
    }
    Chart CreateChart(List items, ChartInfo info);

    client:

    ChartAPI.CreateChart(quotes, new ChartInfo() {
    public int getX(StockQuote q) {
    return q.date.DayOfYear;
    }
    public int getY(StockQuote q) {
    return q.date.price;
    }
    });

    Now you can add more methods to ChartInfo interface which will still look reasonably clean.

    Also to optimize a number of calls you can do

    interface ChartInfo {
    Point getPosition(T o);
    }

    Cheers,
    Vitali

  2. Nice solution. Thought I would point out a minor typo.

    In the Accessor Delegate example you have:
    delegate(StockQuote q) { return q.date.price; } );

    which should be:
    delegate(StockQuote q) { return q.price; } );

    I do like the example. Delegates continue to elude me and this helps.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s