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
:
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:
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:
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:
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:
#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:
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.