Affordances: Specialized Collections
This post is part of an ongoing series about Affordances and Programming Languages.
Most programming languages provide a library of collection types for use in our programs, such as arrays, lists, sets, and hashes/maps.
Often, we need to implement a domain class that acts like a built-in collection, but also provides some domain-specific behavior.
The first solution a new programmer might reach for is to subclass from the desired collection type. This can work, but is generally not the best design. Many times, these collection-like domain classes need to inherit from some other domain class instead, and in most languages, you only get to play the inheritance card once.
A better solution is for the domain class to have an instance variable of the desired collection type and to delegate any collection messages to the internal collection.
As much as I hate to admit it, Smalltalk doesn’t provide affordances for this. It forces us to manually implement any of the collection messages we want to expose to clients of the domain class:
C++ fares better because collection operations are template functions that operate on a pair of iterators, rather than methods on a particular class. All we have to do in our domain class is expose the iterators:
Having to define both const
and non-const
versions of the methods
is somewhat painful, but less so than having to implement all of the
collection methods.
Ruby, with its mixins and the Enumerable
module,
provides really nice affordances for this pattern:
Enumerable
implements all of the collection methods in terms of
each
, so as long as we provide a reasonable implementation of
each
, Enumerable
takes care of the rest for us.
If we also make use of the Forwardable
module, we can allow it to
implement our delegating methods for us too:
Because of the affordances that Ruby and C++ provide, it is easier to provide a complete solution where there are no surprises about which collection methods are available on the domain class. Since Smalltalk doesn’t provide these affordances, it is likely that we’ll only implement a few of the collection methods rather than the full set.