30. Designing a small class
You can build classes, make instances, and inherit. This last chapter of Part 6 is not about new syntax — it is about taste: designing a class that is pleasant and safe to use. The big idea, encapsulation, is simpler than it sounds.
Encapsulation: let the methods guard the data
Encapsulation means keeping an object's data valid by changing it only through its own methods. The outside world asks the object to do things; it never reaches in to scramble the fields.
Here is a BankAccount. The balance should never go
negative, and you cannot deposit a negative amount. The methods
enforce those rules:
local Account = {}
Account.__index = Account
function Account.new(owner)
return setmetatable({ owner = owner, balance = 0 }, Account)
end
function Account:deposit(amount)
if amount > 0 then
self.balance = self.balance + amount
end
end
function Account:withdraw(amount)
if amount > 0 and amount <= self.balance then
self.balance = self.balance - amount
return true -- success
end
return false -- not enough money, or a silly amount
end
function Account:getBalance()
return self.balance
endUsing it:
local acc = Account.new("Keiko")
acc:deposit(100)
acc:withdraw(30)
print(acc:getBalance()) -- 70
print(acc:withdraw(1000)) -- false (refused: not enough)
print(acc:getBalance()) -- 70 (unchanged)The account can never reach an impossible state: every change goes
through a method that checks first. If outside code could write
acc.balance = -500 directly, that guarantee is gone. So the
deal is: talk to the object through its methods.
A clean public surface
Think of a class as having two sides:
- The inside — its fields and helper logic. Details that may change.
- The outside — the methods other code calls. A
small, clear set of actions:
deposit,withdraw,getBalance.
A good class keeps the outside small and obvious. A user of your
Account needs only three verbs; they never need to know the
balance lives in a field called balance. Store it in cents
tomorrow, and as long as the three methods behave the same, nothing else
breaks.
Name methods as actions
Methods do things, so name them with verbs:
deposit, withdraw, move,
attack, reset. Fields hold things, so
name them with nouns: balance, name,
hp. acc:withdraw(30) should read like an
instruction — it is one.
Open exercises/30/01-account.lua. It has the
BankAccount class. Add a :canAfford(amount)
method that returns true if the balance is at least
amount, without touching it. Use it before a withdraw.
Three questions for any class
When you design a class, ask:
- What does it know? → its fields (an account knows its balance).
- What can it do? → its methods (deposit, withdraw, check).
- What must always stay true? → the rules the methods protect (the balance is never negative).
Answer those three and the class almost writes itself.
Homework
Homework files: exercises/30/homework/.
Problem 1 — Counter class
Open exercises/30/homework/01-counter.lua. Design a
Counter that starts at 0 with
:increment(), :get(), and
:reset(). The count must change only through those methods.
Show it counting, reading, and resetting.
Problem 2 — Clamped health
Open exercises/30/homework/02-health.lua. Design a
Health class with a max. :heal(n)
raises health but never above max; :damage(n)
lowers it but never below 0; :get() reads it.
The rule to protect: health stays between 0 and
max.
Problem 3 — Light switch
Open exercises/30/homework/03-switch.lua. Design a
Switch that is on or off. :toggle() flips it,
:isOn() reports its state. Start it off, toggle a few
times, and print the state after each.
Challenge — Stack with a guard
Open exercises/30/homework/04-stack.lua. Design a
Stack with :push(v), :pop(), and
:size(). The rule to protect: :pop() on an
empty stack must not crash — return nil instead. Show it
working, including a pop on an empty stack.
Stuck or finished? Open the homework solutions page.