Software! Math! Data! The blog of R. Sean Bowman
The blog of R. Sean Bowman
November 08 2015

Software design patterns have gone from something of a phenomenon to requisite knowledge for any competent programmer. Their history is fascinating, though, and rooted in abstractions people noticed after writing and looking at lots and lots of code. Smart people figured out common elements, and then were able to describe them eloquently to the rest of us. Let’s have a look at a few important patterns, including some history.

When I was in high school, I learned C++. Or so I thought. At some point I got a copy of James Coplien’s Advanced C++ Programming Styles and Idioms. I read it, in pieces, over and over. I never came close to understanding most of it. But I realized that (to make an analogy), although I had learned the rules of chess, I had no clue how to play.

Coplien’s book was one of the first places I saw truly incredible things done with code, that code wasn’t just a way of expressing a solution to a problem. Code could also be extensible, easier to modify or maintain, more performant, more closely model the real world problems you’re dealing with. Most importantly, code could adhere to a set of best practices – avoiding mistakes people before you have already solved is crucial. Coplien was one of the first to recognize and write down common idioms he saw in good code. “Idioms” isn’t quite the right word, because these are larger structural patterns in software. Coplien attempted to explicitly codify these patterns using a “problem, motivation, solution” type of approach, an approach later widely embraced in many fields.

The Envelope/Letter Pattern

One pattern that has stuck with me through the years is the "Envelope/Letter" pattern. Here, the “envelope” is the class that clients interface with. It holds the interface used externally. The “letter” is an object held by the envelope that provides most of the actual semantics (and code!) for the system.

Here’s a great example Coplien gives in his book. We have a Number type with several subtypes: perhaps integers, rational numbers, real numbers, complex numbers. (Here I’ve only done integers and real numbers to keep the code small.) Among the major issues at hand is this: when we add an integer to a real number, the result is a real number. How do we maintain a consistent interface when the results of operations on the types can fundamentally change the types themselves?

Let’s have a look at the classes themselves:

class Real;
class Integer;

class Number {
    friend Real;
    friend Integer;
public:
    Number() {}

    virtual Number add(const Number &n) const;
    virtual Number realAdd(const Number &) const;
    virtual Number intAdd(const Number& n) const;
    virtual void print() const;

    static Number makeReal(float r);
    static Number makeInt(int v);
protected:
    unique_ptr<Number> rep;
};

class Real : public Number {
    friend Integer;
    friend Number;

    float val;

    Real(float r) : val(r) {}
    virtual Number add(const Number &n) const;
    virtual Number realAdd(const Number &) const;
    virtual Number intAdd(const Number& n) const;
    virtual void print() const;
};

class Integer : public Number {
    friend Real;
    friend Number;

    int val;
    Integer(int n) : val(n) {}
    virtual Number add(const Number &n) const;
    virtual Number realAdd(const Number &) const;
    virtual Number intAdd(const Number& n) const;
    virtual void print() const;
};

Note that the members of Real and Integer are not public; the only interface the client sees is that of Number. Furthermore, Number stores only a pointer to another Number! That will turn out to be one of the "concrete" classes we define, like Real and Integers, representing real numbers and integers, respectively.

Implementation

How about the implementations of these methods? Not so difficult, it turns out. First, we need a way to make real numbers and integers; this is done through static methods of the Number class.

Number Number::makeReal(float f) {
    Number n;
    n.rep.reset(new Real(f));
    return n;
}

Number Number::makeInt(int v) {
    Number n;
    n.rep.reset(new Integer(v));
    return n;
}

See how the pointer in the Number class (the “envelope”) points to the object that actually holds the semantics (the “letter”)? Now that we’ve got this, all we need is ways to add things and coerce them, if necessary, to new types.

Number Number::add(const Number& n) const {
    return rep->add(n);
}

Number Number::realAdd(const Number& n) const {
    return rep->realAdd(n);
}

Number Number::intAdd(const Number& n) const {
    return rep->intAdd(n);
}

void Number::print() const {
    rep->print();
}


Number Real::add(const Number& n) const {
    return n.realAdd(*this);
}

Number Real::realAdd(const Number& n) const {
    const Real *rn = dynamic_cast<const Real *>(&n);
    return Number::makeReal(this->val + rn->val);
}

Number Real::intAdd(const Number& n) const {
    const Integer *it = dynamic_cast<const Integer *>(&n);
    return Number::makeReal(this->val+ it->val);
}

void Real::print() const {
    cout << "R(" << val << ")";
}


Number Integer::add(const Number& n) const {
    return n.intAdd(*this);
}

Number Integer::intAdd(const Number& n) const {
    const Integer *it = dynamic_cast<const Integer *>(&n);
    return Number::makeInt(this->val+ it->val);
}

Number Integer::realAdd(const Number& n) const {
    const Real *rn = dynamic_cast<const Real *>(&n);
    return Number::makeReal(this->val + rn->val);
}

void Integer::print() const {
    cout << "I(" << val << ")";
}

When we add two numbers, the addition is delegated to the rep pointer. This in turn knows which method to delegate to, and constructs a new Number whose rep holds a pointer to an object of the appropriate type.

You can see the whole file as a gist here, including tests that show the type of number changes according to it summands.

Now if you’ve seen some design patterns before, this might look familiar… How does it relate to the patterns in the GoF “Design Patterns” book? I’m not 100% sure, but I intend to find out!

Approx. 932 words, plus code