Reuse Without Inheritance

Dear Computer

Chapter 5: Objects

Reuse Without Inheritance

Subclassing can get us in trouble. Suppose we are writing code to support a raffle. People buy a ticket and get their name entered in a list. Later on, we draw one of the names at random and award that person a prize. The code we need has mostly already been written in Ruby's Array class, so we decide to reuse that code by making our Raffle class a subclass of Array:

Ruby
class Raffle < Array
  def enter(name)
    push(name)
  end

  def draw
    index = rand(size)
    delete_at(index)
  end
end

raffle = Raffle.new
raffle.enter('Angelica')
puts raffle.draw           # prints Angelica

Except we don't write it quite like this. A bad day has got us spiralling downward, and we think back to our first break-up. It wasn't our idea to end the relationship, and we harbor some resentment. So, we decide to silently stop our ex—and any unfortunate soul with the same name—from entering the raffle:

Ruby
class Raffle < Array
  def enter(name)
    if name != 'Jill'
      push(name)
    end
  end

  def draw
    index = rand(size)
    delete_at(index)
  end
end

raffle = Raffle.new
10000.times { raffle.enter('Jill') }
raffle.enter('Angelica')
puts raffle.draw           # prints Angelica

No matter how many tickets our ex buys, the winner will always be someone else. However, the fog of war has gotten the better of us, as our ex can exploit inheritance to circumvent our filter:

Ruby
raffle = Raffle.new
raffle.push('Jill')
puts raffle.draw           # prints Jill

Our raffle inherits from Array, so the entirety of the superclass's public interface is available on any Raffle object. Our ex doesn't need to go through the enter method to get their name added. The inherited push method works just fine. Perhaps we should have sought therapy instead of retaliation.

Inheritance is a clumsy mechanism if all we are trying to do is reuse code. Since we can't say that a Raffle should support all the behaviors of an Array—that it is an Array—then inheritance is not the vehicle we want in order to reuse code.

One solution is to use composition instead of inheritance. An Array becomes part of the private state of the Raffle instead of its superclass foundation:

Ruby
class Raffle
  def initialize
    @names = []
  end

  def enter(name)
    if name != 'Jill'
      @names.push(name)
    end
  end

  def draw
    index = rand(@names.size)
    @names.delete_at(index)
  end
end

raffle = Raffle.new
raffle.push('Jill')      # fails

The only behaviors available on a Raffle object are the ones we define and the harmless ones we inherit from Ruby's Object class.

C++ offers another solution through private inheritance. Syntactically, a subclass privately inherits from a superclass in same way as normal inheritance, except the modifier private precedes the name of the superclass:

C++
#include <iostream>
#include <vector>

using namespace std;
using std::vector;

class Raffle : private vector<string> {
  public:
    void enter(const string& name) {
      if (name != "Jill") {
        push_back(name);
      }
    }

    string draw() {
      int index = (int) ((rand() / (double) RAND_MAX) * size());
      return *erase(begin() + index);
    }
};

The methods of the private superclass may be called from within the subclass methods, as are push_back, size, erase, and begin. However, no code outside the class can call the inherited methods on an instance:

C++
int main() {
  Raffle raffle;
  raffle.enter("Jill");           // no effect
  // raffle.push_back("Jill")     <- illegal!
  raffle.enter("Luisa");
  cout << raffle.draw() << endl;  // prints Luisa
  return 0;
}

C++ is one of the few languages that supports private inheritance. It's not more widely adopted because many software engineers and language designers favor composition. Inheritance, private or public, suggests that the subclass is situated in an is-a relationship with the superclass. Is-a semantics mean that a subtype can be used wherever a supertype is expected.

← Overriding BehaviorsBootstrapping Classes →