Object-Oriented Programming

17.1 The Colon Syntax

local obj = {value = 10}
function obj.get(self) return self.value end     -- explicit self
function obj:get2() return self.value end        -- colon adds an implicit self parameter
print(obj.get(obj), obj:get(), obj:get2())       -- obj:get() == obj.get(obj)

17.2 Classes with Metatables

__tostring is looked up in the metatable (Account), so defining it as a method on Account works because Account is the metatable.

local Account = {}
Account.__index = Account                         -- instances look up methods in Account

function Account.new(owner, balance)
    local self = setmetatable({}, Account)
    self.owner = owner
    self.balance = balance or 0
    return self
end

function Account:deposit(amount)
    if amount <= 0 then error("amount must be positive", 2) end
    self.balance = self.balance + amount
    return self                                   -- allow chaining
end

function Account:withdraw(amount)
    if amount > self.balance then return nil, "insufficient funds" end
    self.balance = self.balance - amount
    return true
end

function Account:__tostring() return string.format("Account(%s, %.2f)", self.owner, self.balance) end

local acc = Account.new("Ana", 100)
acc:deposit(50):deposit(25)
local ok, err = acc:withdraw(500)
print(acc, ok, err)        --> Account(Ana, 175.00)  nil  insufficient funds

17.3 Class Variables, Static Methods and Privacy

local Counter = {count = 0}            -- class-level field
Counter.__index = Counter
function Counter.new() Counter.count = Counter.count + 1; return setmetatable({}, Counter) end
function Counter.total() return Counter.count end    -- "static" (no self)

-- Privacy via closures
local function new_person(name)
    local secret = "hidden"              -- private
    local self = {}
    function self.get_name() return name end
    function self.reveal() return secret end
    return self
end
local p = new_person("Bo")
print(p.get_name(), p.secret)          --> Bo  nil

17.4 Prototype-Based Style

local Animal = {sound = "..."}
function Animal:new(o)
    o = o or {}
    setmetatable(o, self)                -- self is the prototype (class or parent instance)
    self.__index = self
    return o
end
function Animal:speak() return self.name .. " says " .. self.sound end

local dog = Animal:new{name = "Rex", sound = "Woof"}
print(dog:speak())