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.")
end

A 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
end

Dog 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 rexDogAnimal, 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.