When All You Have is a Hammer...
…screws don’t make much sense.
We’re all familiar with the adage that says when all you
have is a hammer, everything looks like a nail.
That’s
true enough as far as it goes. We tend to frame problems in terms
of what we know. However, there’s an even more pernicious
effect: we don’t recognise the utility of solutions outside
our experience. If all you have is a hammer, screws seem like a
distinctly sub-optimal type of nail: they require more force to
hammer in, they damage the wood and they don’t hold as well.
Once you have a screwdriver, you realise that screws are actually
superior to nails for many tasks.
The
article inheritance
is evil and must be destroyed is written by someone who only knows
about hammers. He uses the example of a class representing a ball;
it’s further subclassed into bouncing balls and balls which fade
in and out. But what if one wants to have a bouncing, fading ball?
He writes using inheritance, the code that handles bouncing and
fading is locked up in the BouncingBall and FadingBall classes and
can’t be used elsewhere.
Well, in a language which only
allows single inheritance, sure: you can only inherit from one
superclass, and so it’s not possible to have a bouncing fading
ball which inherits from both bouncing and fading balls.
Fortunately, we’ve had a well-defined object system since the late 1980s. It allows multiple inheritance, and all one has to do is inherit behaviour from both superclasses and Everything Works. An example:
(defclass ball ()
((x
:accessor x
:initform 0
:initarg :x)
(y
:accessor y
:initform 0
:initarg :y)
(colour
:accessor colour
:initform 'white
:initarg :colour)))
(defclass bouncing-ball (ball)
((elasticity
:accessor elasticity
:initform 1
:initarg :elasticity)
vector))
(defclass fading-ball (ball)
((fade-rate
:accessor fade-rate
:initform 1
:initarg :fade-rate)))
(defclass fading-bouncing-ball (fading-ball bouncing-ball) ())
(defgeneric draw (ball)
(:documentation "Draw BALL at its position"))
(defmethod draw ((ball ball))
"Draw BALL at its position, with the proper colour."
(draw-circle (x ball) (y ball)) (colour ball))
(defmethod draw :before ((ball bouncing-ball))
"Move BALL to the next position as it bounces."
(with-slots (vector x y) ball
(setf x (bounce-x x vector)
y (bounce-y y vector))))
(defmethod colour :around ((ball fading-ball))
"Return BALL’s colour faded for this point in time."
(fade-colour (call-next-method ball) (fade-rate ball) (get-current-time)))
And yes, everything works exactly as expected. The DRAW method specialised on balls draws a circle wherever it needs to; the DRAW before-method specialised on bouncing balls runs before a bouncing ball is drawn; it updates its current position and then the primary DRAW method is called, drawing the ball at the new position. The COLOUR around-method specialised on fading balls calls the normal COLOUR method, figures out the faded colour and then returns it. Yes, an instance of FADING-BOUNCING-BALL inherits from both FADING-BALL and BOUNCING-BALL, and they both inherit from BALL. It all just works.
You can either keep on trying to hammer in screws, or you can learn how to use a screwdriver, or you can claim that screws are awful fasteners and that nails are always better. One of these is a better solution than the others.

