29. Inheritance in depth
Chapter 27 touched on inheritance: one class
building on another. It is how you avoid writing the same code twice. A
Dog and a Cat are both Animals —
same name, same way of being described, only differing in the
sound they make. Inheritance writes the shared part once.
The base class
Start with a general class. Everything common to the family lives here:
local Animal = {}
Animal.__index = Animal
function Animal.new(name)
local self = setmetatable({}, Animal)
self.name = name
return self
end
function Animal:describe()
print(self.name .. " is an animal.")
endA child class that inherits
A child class aims its lookups at the base — the two-line setup from Chapter 27:
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog.new(name)
local self = Animal.new(name) -- build with Animal's fields
return setmetatable(self, Dog) -- but tagged as a Dog
endDog has no describe of its own, but its
metatable's __index points at Animal, so a
Dog instance finds it there:
local rex = Dog.new("Rex")
rex:describe() -- Rex is an animal. (inherited from Animal)The lookup walks rex → Dog →
Animal, stopping at the first place it finds
describe.
Adding behaviour the child alone has
Give the child methods the parent lacks. They live on the child class, invisible to parent and siblings:
function Dog:fetch()
print(self.name .. " fetches the ball.")
end
rex:fetch() -- Rex fetches the ball.Overriding: replacing a parent method
If the child defines a method with the same name as the parent's, the child's version is found first and wins — this is overriding:
function Dog:describe()
print(self.name .. " is a dog.")
end
rex:describe() -- Rex is a dog. (Dog's version, not Animal's)Now the lookup finds describe on Dog and
never reaches Animal.
Calling the parent's version too
Sometimes you want to extend the parent's method, not
replace it: do what it does, then add to it. Call the parent method
directly by name, passing self:
function Dog:describe()
Animal.describe(self) -- do the Animal part first
print(" ...specifically, a dog.") -- then the Dog part
end
rex:describe()
-- Rex is an animal.
-- ...specifically, a dog.The dot form Animal.describe(self) hands in
self directly, running the parent's method on this
instance. This "call up to the parent" is one of the handiest tools in
object-oriented code.
Open exercises/29/01-animals.lua. It has
Animal and Dog. Add a Cat that
inherits from Animal, overrides describe to
say it is a cat, and adds a meow method. Make one cat and
call both.
Homework
Homework files are in exercises/29/homework/.
Problem 1 — Vehicle and Car
Open exercises/29/homework/01-vehicle.lua. Build a base
Vehicle class with .new(name) and a
:describe() that prints
<name> is a vehicle.. Then build a Car
that inherits from it. Make a car and call :describe() — it
should use the inherited method.
Problem 2 — Override
Open exercises/29/homework/02-override.lua. Starting
from the Vehicle/Car classes, override
:describe() on Car to print
<name> is a car. instead. Confirm a
Vehicle still says "vehicle" and a Car says
"car".
Problem 3 — Extend the parent
Open exercises/29/homework/03-extend.lua. Override
Car:describe() so it first calls
Vehicle.describe(self), then prints an extra line,
It has four wheels.. Both lines should appear.
Challenge — Two children
Open exercises/29/homework/04-two-children.lua. Build
Animal plus two children, Bird and
Fish. Each overrides :move() to print how it
gets around (flies, swims). Make one of each,
put them in a list, and loop over it calling :move().
Stuck or finished? Open the homework solutions page.