OOP Basics in Lua
Lua doesn't have built-in classes, but its flexible table and metatable system makes it easy to implement Object-Oriented Programming. You can create classes, objects, methods, and all the OOP patterns you need. In this lesson, you'll learn how to build robust OOP systems in Lua using tables and metatables. Let's get started!
Creating a Class
A class in Lua is typically a table with methods:
-- Define the class
local Person = {}
Person.__index = Person
-- Constructor
function Person:new(name, age)
local self = setmetatable({}, Person)
self.name = name
self.age = age
return self
end
-- Method
function Person:greet()
print("Hello, I'm " .. self.name)
end
-- Create instances
local alice = Person:new("Alice", 25)
local bob = Person:new("Bob", 30)
alice:greet() -- Hello, I'm Alice
bob:greet() -- Hello, I'm Bob
Personis a table that serves as the classPerson.__index = Personmakes methods available to instancesnew()creates an instance withsetmetatable- The colon
:syntax passesselfautomatically
Click Run to execute your code
Instance Methods vs Class Methods
Instance Methods
Methods that operate on individual instances:
local Dog = {}
Dog.__index = Dog
function Dog:new(name, breed)
local self = setmetatable({}, Dog)
self.name = name
self.breed = breed
return self
end
-- Instance method (uses self)
function Dog:bark()
print(self.name .. " says: Woof!")
end
function Dog:getInfo()
return self.name .. " is a " .. self.breed
end
local dog = Dog:new("Buddy", "Golden Retriever")
dog:bark() -- Buddy says: Woof!
Class Methods (Static Methods)
Methods that belong to the class itself:
local Dog = {}
Dog.__index = Dog
Dog.count = 0 -- Class variable
function Dog:new(name, breed)
local self = setmetatable({}, Dog)
self.name = name
self.breed = breed
Dog.count = Dog.count + 1 -- Increment class variable
return self
end
-- Class method (uses dot notation)
function Dog.getCount()
return Dog.count
end
local dog1 = Dog:new("Buddy", "Golden Retriever")
local dog2 = Dog:new("Max", "Labrador")
print("Total dogs:", Dog.getCount()) -- Total dogs: 2
Click Run to execute your code
Encapsulation and Private Variables
Use closures to create private variables:
local function BankAccount(initialBalance)
-- Private variables
local balance = initialBalance
-- Public interface
local self = {}
function self:deposit(amount)
if amount > 0 then
balance = balance + amount
return true
end
return false
end
function self:withdraw(amount)
if amount > 0 and amount <= balance then
balance = balance - amount
return true
end
return false
end
function self:getBalance()
return balance
end
return self
end
local account = BankAccount(1000)
account:deposit(500)
print(account:getBalance()) -- 1500
-- print(account.balance) -- nil (private!)
Getters and Setters
Control access to properties:
local Rectangle = {}
Rectangle.__index = Rectangle
function Rectangle:new(width, height)
local self = setmetatable({}, Rectangle)
self._width = width
self._height = height
return self
end
-- Getter
function Rectangle:getWidth()
return self._width
end
-- Setter with validation
function Rectangle:setWidth(width)
if width > 0 then
self._width = width
else
error("Width must be positive")
end
end
function Rectangle:getArea()
return self._width * self._height
end
local rect = Rectangle:new(10, 5)
print(rect:getArea()) -- 50
rect:setWidth(20)
print(rect:getArea()) -- 100
Click Run to execute your code
Method Chaining
Return self to enable fluent interfaces:
local StringBuilder = {}
StringBuilder.__index = StringBuilder
function StringBuilder:new()
local self = setmetatable({}, StringBuilder)
self.parts = {}
return self
end
function StringBuilder:append(str)
table.insert(self.parts, str)
return self -- Enable chaining
end
function StringBuilder:prepend(str)
table.insert(self.parts, 1, str)
return self -- Enable chaining
end
function StringBuilder:toString()
return table.concat(self.parts)
end
-- Chaining in action
local result = StringBuilder:new()
:append("Hello")
:append(" ")
:append("World")
:prepend(">>> ")
:toString()
print(result) -- >>> Hello World
Practical Examples
Shopping Cart
local ShoppingCart = {}
ShoppingCart.__index = ShoppingCart
function ShoppingCart:new()
local self = setmetatable({}, ShoppingCart)
self.items = {}
return self
end
function ShoppingCart:addItem(name, price, quantity)
table.insert(self.items, {
name = name,
price = price,
quantity = quantity or 1
})
return self
end
function ShoppingCart:getTotal()
local total = 0
for i, item in ipairs(self.items) do
total = total + (item.price * item.quantity)
end
return total
end
function ShoppingCart:getItemCount()
return #self.items
end
local cart = ShoppingCart:new()
cart:addItem("Apple", 1.50, 3)
:addItem("Banana", 0.75, 5)
:addItem("Orange", 2.00, 2)
print("Total:", cart:getTotal()) -- 12.25
print("Items:", cart:getItemCount()) -- 3
Timer Class
local Timer = {}
Timer.__index = Timer
function Timer:new()
local self = setmetatable({}, Timer)
self.startTime = nil
self.elapsed = 0
return self
end
function Timer:start()
self.startTime = os.clock()
return self
end
function Timer:stop()
if self.startTime then
self.elapsed = os.clock() - self.startTime
self.startTime = nil
end
return self
end
function Timer:getElapsed()
if self.startTime then
return os.clock() - self.startTime
end
return self.elapsed
end
local timer = Timer:new()
timer:start()
-- Do some work...
timer:stop()
print("Elapsed:", timer:getElapsed(), "seconds")
Click Run to execute your code
Practice Exercise
Try these OOP challenges:
Click Run to execute your code
Summary
In this lesson, you learned:
- Creating classes using tables and metatables
- Constructors with
new()method - Instance methods vs class methods
- Encapsulation with closures for private variables
- Getters and setters for controlled access
- Method chaining for fluent interfaces
- Practical examples: ShoppingCart, Timer
What's Next?
You've learned the basics of OOP in Lua! Next, we'll explore inheritanceโhow to create class hierarchies, extend functionality, and implement polymorphism. Let's continue! ๐
Enjoying these tutorials?