Inheritance
Inheritance lets you create new classes based on existing ones, reusing and extending their functionality.
Single Inheritance
Use extends to inherit from a parent class:
local d = Dog.new()
d.name = "Buddy"
d.breed = "Labrador"
d:speak() -- Buddy makes a sound (inherited)
d:bark() -- Buddy barks! (own method)
Multiple Inheritance
Inherit from multiple classes by separating names with commas:
local swimmer = class("Swimmer")
swimmer.canSwim = true
function swimmer:swim()
print("Swimming!")
end
swimmer:register()
local flyer = class("Flyer")
flyer.canFly = true
function flyer:fly()
print("Flying!")
end
flyer:register()
local duck = class("Duck", {extends = "Animal, Swimmer, Flyer"})
function duck:quack()
print(self.name .. " quacks")
end
duck:register()
local d = Duck.new()
d.name = "Donald"
d:speak() -- Donald makes a sound (from Animal)
d:swim() -- Swimming! (from Swimmer)
d:fly() -- Flying! (from Flyer)
d:quack() -- Donald quacks (own method)
-- Access inherited properties
print(d.canSwim) -- true
print(d.canFly) -- true
Polymorphism
Parent methods have access to all inherited members. A method defined in Swimmer can access name from Animal when called on a Duck instance, because self refers to the full Duck instance with all its inherited members.
Accessing Parent Members
Access parent instances using self.ParentName:
class "Vehicle" {
speed = 0;
accelerate = function(self, amount)
self.speed = self.speed + amount
end;
}
class "Car" extends "Vehicle" {
wheels = 4;
turboBoost = function(self)
-- Access parent method
self.Vehicle:accelerate(50)
print("Turbo! Speed: " .. self.speed)
end;
}
local car = Car.new()
car:accelerate(20) -- speed = 20
car:turboBoost() -- Turbo! Speed: 70
Calling Parent Constructors
When a child has a constructor, call the parent constructor explicitly:
class "Entity" {
id = 0;
__construct = function(self, entityId)
self.id = entityId
print("Entity created: " .. self.id)
end;
}
class "Player" extends "Entity" {
name = "";
__construct = function(self, playerId, playerName)
-- Call parent constructor
self.Entity(playerId)
self.name = playerName
print("Player created: " .. self.name)
end;
}
local p = Player.new(42, "Alice")
-- Entity created: 42
-- Player created: Alice
Method Overriding
Child classes can override parent methods:
class "Shape" {
describe = function(self)
print("I am a shape")
end;
}
class "Circle" extends "Shape" {
radius = 0;
describe = function(self)
print("I am a circle with radius " .. self.radius)
end;
}
local s = Shape.new()
s:describe() -- I am a shape
local c = Circle.new()
c.radius = 5
c:describe() -- I am a circle with radius 5
Calling Overridden Parent Method
Use self.ParentName:method() to call the parent's version:
class "Animal" {
describe = function(self)
return "an animal"
end;
}
class "Cat" extends "Animal" {
describe = function(self)
return self.Animal:describe() .. " called cat"
end;
}
local c = Cat.new()
print(c:describe()) -- an animal called cat
Polymorphism
SIMPLOO fully supports polymorphism - when a parent method calls self:method(), it uses the child's override if one exists. See the Polymorphism guide for details and design pattern examples.
Checking Inheritance
Use instance_of to check class relationships:
class "A" {}
class "B" extends "A" {}
class "C" extends "B" {}
local c = C.new()
print(c:instance_of(C)) -- true
print(c:instance_of(B)) -- true
print(c:instance_of(A)) -- true
local a = A.new()
print(a:instance_of(C)) -- false
Ambiguous Members
When multiple parents have the same member name, accessing it causes an error:
class "Left" {
value = "left";
}
class "Right" {
value = "right";
}
class "Both" extends "Left, Right" {}
local b = Both.new()
print(b.value) -- Error: call to member value is ambiguous
Resolve by accessing through the specific parent:
Or override in the child:
class "Both" extends "Left, Right" {
value = "both"; -- Overrides both parents
}
local b = Both.new()
print(b.value) -- both
Parents with Same Short Name
When extending two parents from different namespaces that have the same class name (e.g., ns1.Util and ns2.Util), the short name Util becomes nil (ambiguous). Use using ... as to give them unique aliases:
namespace "ns1"
class "Util" { getValue = function(self) return 1 end; }
namespace "ns2"
class "Util" { getValue = function(self) return 2 end; }
namespace ""
using "ns1.Util" as "Util1"
using "ns2.Util" as "Util2"
class "MyClass" extends "Util1, Util2" {}
local m = MyClass.new()
m.Util1:getValue() -- 1
m.Util2:getValue() -- 2
m.Util -- nil (ambiguous)
Alternatively, you can access parents via their full name using bracket notation:
Deep Inheritance Chains
Inheritance can go multiple levels deep. Use varargs (...) to pass arguments up the chain cleanly:
class "Level1" {
value1 = 0;
__construct = function(self, v, ...)
self.value1 = v
end;
}
class "Level2" extends "Level1" {
value2 = 0;
__construct = function(self, v, ...)
self.Level1(...) -- Pass remaining args to parent
self.value2 = v
end;
}
class "Level3" extends "Level2" {
value3 = 0;
__construct = function(self, v, ...)
self.Level2(...) -- Pass remaining args to parent
self.value3 = v
end;
}
-- Arguments are consumed from right to left: v3, v2, v1
local obj = Level3.new(3, 2, 1)
print(obj.value1) -- 1
print(obj.value2) -- 2
print(obj.value3) -- 3