Inheritance, Mixins and Class Libraries

18.1 Single Inheritance

local Animal = {}
Animal.__index = Animal
function Animal.new(name) return setmetatable({name = name}, Animal) end
function Animal:speak() return self.name .. " makes a sound" end
function Animal:describe() return "I am " .. self.name end

local Dog = setmetatable({}, {__index = Animal})   -- Dog inherits from Animal
Dog.__index = Dog
function Dog.new(name, breed)
    local self = Animal.new(name)                   -- call the parent constructor
    self.breed = breed
    return setmetatable(self, Dog)
end
function Dog:speak()                                -- override
    return self.name .. " barks"
end
function Dog:describe()
    return Animal.describe(self) .. ", a " .. self.breed   -- call the parent method ("super")
end

local d = Dog.new("Rex", "Labrador")
print(d:speak(), d:describe())
print(getmetatable(d) == Dog, getmetatable(Dog).__index == Animal)

local function is_instance(obj, class)
    local mt = getmetatable(obj)
    while mt do
        if mt == class then return true end
        local parent = getmetatable(mt)
        mt = parent and parent.__index
    end
    return false
end
print(is_instance(d, Animal))   --> true

18.2 A Reusable class() Helper

Metamethods are looked up directly in the metatable (raw access), not via __index. Subclasses must copy them.

local function class(parent)
    local c = {}
    c.__index = c
    if parent then setmetatable(c, {__index = parent}) end
    c.super = parent
    c.new = function(...)
        local instance = setmetatable({}, c)
        if instance.init then instance:init(...) end
        return instance
    end
    return c
end

local Shape = class()
function Shape:init(name) self.name = name end
function Shape:area() return 0 end
function Shape:__tostring() return self.name .. " with area " .. self:area() end

local Circle = class(Shape)
function Circle:init(r) Shape.init(self, "circle"); self.r = r end
function Circle:area() return math.pi * self.r ^ 2 end
Circle.__tostring = Shape.__tostring    -- metamethods aren't inherited through __index: copy them

print(Circle.new(2))

18.3 Multiple Inheritance and Mixins

local function search(k, parents)
    for _, p in ipairs(parents) do
        local v = p[k]
        if v ~= nil then return v end
    end
end
local function create_class(...)
    local parents = {...}
    local c = {}
    setmetatable(c, {__index = function(t, k) local v = search(k, parents); t[k] = v; return v end})
    c.__index = c
    return c
end

local Serializable = {serialize = function(self) return "serialized:" .. tostring(self.id) end}
local Comparable = {equals = function(a, b) return a.id == b.id end}
local function include(class, mixin) for k, v in pairs(mixin) do class[k] = v end return class end
local Model = include(include({}, Serializable), Comparable)

18.4 Class Libraries

Popular, full-featured (mixins, metamethod inheritance, isInstanceOf) Tiny class library classic (rxi) Minimal (used often with LÖVE) Penlight pl.class Part of the Penlight utility library From the HUMP library (LÖVE)

local class = require("middleclass")
local Fruit = class("Fruit")
function Fruit:initialize(sweetness) self.sweetness = sweetness end
local Lemon = class("Lemon", Fruit)
function Lemon:initialize() Fruit.initialize(self, 1) end
local lemon = Lemon:new()
print(lemon:isInstanceOf(Fruit), lemon.class.name)    --> true  Lemon