Access via Closures
Part of the Object Oriented Programming section of Coddy's Lua journey — lesson 45 of 70.
Private local variables are useless if you can never interact with them. The solution is to define getter and setter methods inside the constructor—where they have access to the closure's scope.
Since these methods are created inside :new(), they can "see" the local variables. You assign them directly to the object:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Private variable
local obj = {}
-- Define methods inside the constructor
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endNow the private count variable can be accessed—but only through the methods you provide:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (still hidden!)Notice that :getCount() and :increment() are defined on obj directly, not on the class table. Each instance gets its own copy of these functions, each with access to its own private count. This is the trade-off: true privacy costs a bit more memory since methods aren't shared through the prototype.
Challenge
EasyLet's build a Temperature class that uses closure-based privacy to store a temperature value in Celsius, with getter and setter methods defined inside the constructor to provide controlled access!
You'll organize your code across two files:
Temperature.lua: Create a class where the temperature value is stored in a local variable inside the constructor—completely hidden from outside access. The constructor:new(initialCelsius)should store the temperature in a local variable, then define these methods directly on the object instance::getCelsius()— returns the current temperature in Celsius:setCelsius(value)— updates the temperature to a new value:getFahrenheit()— returns the temperature converted to Fahrenheit using the formula:(celsius * 9/5) + 32
main.lua: Require your Temperature module and read two values from input: an initial temperature and a new temperature to set. Create a Temperature instance with the initial value, print the Celsius and Fahrenheit readings, then use the setter to change the temperature and print both readings again.
You will receive two inputs:
- The initial temperature in Celsius (a number)
- A new temperature to set (a number)
Your output should be four lines showing the temperature readings before and after the change:
Celsius: {initialValue}
Fahrenheit: {convertedInitial}
Celsius: {newValue}
Fahrenheit: {convertedNew}For example, if the inputs are 0 and 100, the output should be:
Celsius: 0
Fahrenheit: 32
Celsius: 100
Fahrenheit: 212Remember: the temperature value should be stored in a local variable inside :new(), not on self. The getter and setter methods are defined inside the constructor where they can access this private variable through closure. Attempting to access temp.celsius or temp._celsius directly should return nil!
Cheat sheet
To create truly private variables in Lua classes, define getter and setter methods inside the constructor where they have access to local variables through closure.
Methods are assigned directly to the object instance (not the class table), giving each instance its own copy with access to its own private variables:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Private variable
local obj = {}
-- Define methods inside the constructor
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endUsage:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (still hidden!)Trade-off: Each instance gets its own copy of the methods (not shared through the prototype), which uses more memory but provides true privacy.
Try it yourself
-- Require the Temperature module
local Temperature = require('Temperature')
-- Read input values
local initialTemp = tonumber(io.read())
local newTemp = tonumber(io.read())
-- TODO: Create a Temperature instance with the initial value
-- TODO: Print the initial Celsius and Fahrenheit readings
-- TODO: Use the setter to change the temperature to newTemp
-- TODO: Print the updated Celsius and Fahrenheit readings
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1The 'Self' Concept
Tables with FunctionsExplicit 'self'The Colon SyntaxDot vs ColonRecap - Moving Point4Project: Digital Bank
Project SetupDeposit Method7Polymorphism & Overriding
Overriding MethodsCalling Parent MethodsDuck TypingCommon InterfaceChecking TypeRecap - Employee Roles10Project: Shape Manager
Project SetupRectangle Class2Class Prototype Pattern
The Prototype ConceptLinking with __indexThe :new() ConstructorInitializing AttributesIndependent InstancesRecap - Car Factory5Operator Overloading in OOP
Adding ObjectsSubtracting ObjectsConcatenating ObjectsComparing Objects (<, >)Recap - Wallet Math8Encapsulation
Naming ConventionsClosures for PrivacyAccess via ClosuresRead-Only TablesValidation LogicRecap - Secure Vault11Design Patterns (Lite)
Factory FunctionsSingleton TableIterator PatternObserver (Listener)Recap - Logger Factory3Object State and Behavior
Instance VariablesGetter MethodsSetter MethodsCalculated PropertiesFormatting StringsEquality ChecksRecap - Student Grade6Inheritance Basics
The Inheritance SetupInheriting MethodsExtending the ConstructorAdding Child MethodsShared vs UniqueRecap - Shape Hierarchy