Null objects, not values

Spread the love

The Null Object pattern is oft-cited but not-oft-enough used (in my experience). After all, which looks more elegant/maintainable/object-oriented?

// Null values
public IEnumerable<Result> GetFilteredResults()
{
    IEnumerable<Result> results = this.GetAllResults();

    // Callee can return a null sequence, need to be defensive.
    if (results != null)
    {
        IFilter filter = this.GetFilter();
        foreach (Result result in results)
        {
            // Filter may have been undefined -- check explicitly.
            if ((filter == null) || filter.Passes(result))
            {
                yield return result;
            }
        }
    }
}

// Null objects
public IEnumerable<Result> GetFilteredResults()
{
    // Is it a real filter or a 'Null' filter? Who cares!
    IFilter filter = this.GetFilter();

    // Is the results sequence empty? Doesn't matter!
    foreach (Result result in this.GetAllResults())
    {
        if (filter.Passes(result))
        {
            yield return result;
        }
    }
}

The difference is subtle but important. Not only do null checks go away in the Null Object world, but overall responsibilities become more explicit and unambiguous — “run a filter against a sequence, that’s all” as opposed to “act as a pass all filter if none provided, otherwise run the filter as usual.”

When Null Object is eschewed, “performance overhead” is typically cited. Returning an object might mean allocating memory. Too many heap allocations in a particularly hot path may lead to an overworked garbage collector. Such concerns are sometimes (or perhaps most of the time?) imaginary, but if backed up by measurement, they are of course worth consideration.

Allocation’s usual antidote is caching. The Null version of your object could be a pre-created instance that you hand out as needed, e.g.:

class PassAllFilter : IFilter
{
    public bool Passes(Result result)
    {
        return true;
    }
}

class MyClass
{
    // We'll allocate only one of these filters per AppDomain,
    // no matter how many MyClass instances are created.
    private static readonly IFilter DefaultFilter = new PassAllFilter();

    public MyClass()
    {
        this.Filter = DefaultFilter;
    }

    public IFilter Filter { get; set; }
// ...
}

This works fine in the above case for at least two reasons: the IFilter implementation is stateless/immutable, and there is no real penalty for eagerly constructing it (in other words, we don’t benefit much from laziness).

But let’s take a look at the GetFilteredResults code again. The dominant cost here is far more likely to be the construction of a new sequence/enumerator on each call due to the use of an iterator method. Perhaps that is the more pressing concern to be addressed. One possible solution lies in the Tell Don’t Ask (TDA) style. Rather than returning raw data, we preserve encapsulation and have the caller tell us what to do with the data:

// VOID return
public void ForEachFilteredResult(Action<Result> action)
{
    // Still using Null Object here, of course!
    IFilter filter = this.GetFilter();
    foreach (Result result in this.GetAllResults())
    {
        if (filter.Passes(result))
        {
            // Apply the caller's operation here
            action(result);
        }
    }
}

// Sample use case
ForEachFilteredResult(r => PrintResult(r.ToString()));

There are still possible sources of overhead here. Passing the action as a delegate may involve implicitly capturing surrounding local state variables. This is why a lot of delegates end up accepting an extra Object parameter to pass user-defined data, e.g. in Task.ContinueWith.

To recap: Null Objects are a great tool for better OO, but the performance gains you seek may point you in an entirely different (but even more strongly object-oriented) direction.

Leave a Reply

Your email address will not be published. Required fields are marked *