Acceso mediante Closures
Parte de la sección Object Oriented Programming del Journey de Lua de Coddy — lección 45 de 70.
Las variables locales privadas son inútiles si nunca puedes interactuar con ellas. La solución es definir métodos getter y setter dentro del constructor, donde tienen acceso al ámbito del closure.
Dado que estos métodos se crean dentro de :new(), pueden "ver" las variables locales. Los asignas directamente al objeto:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Variable privada
local obj = {}
-- Definir métodos dentro del constructor
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endAhora se puede acceder a la variable privada count, pero solo a través de los métodos que proporciones:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (¡sigue oculto!)Observa que :getCount() y :increment() están definidos directamente en obj, no en la tabla de la clase. Cada instancia obtiene su propia copia de estas funciones, cada una con acceso a su propio count privado. Este es el compromiso: la verdadera privacidad cuesta un poco más de memoria, ya que los métodos no se comparten a través del prototipo.
Desafío
Fácil¡Vamos a construir una clase Temperature que utilice privacidad basada en cierres (closures) para almacenar un valor de temperatura en Celsius, con métodos getter y setter definidos dentro del constructor para proporcionar un acceso controlado!
Organizarás tu código en dos archivos:
Temperature.lua: Crea una clase donde el valor de la temperatura se almacene en una variable local dentro del constructor, completamente oculta del acceso externo. El constructor:new(initialCelsius)debe almacenar la temperatura en una variable local y luego definir estos métodos directamente en la instancia del objeto::getCelsius()— devuelve la temperatura actual en Celsius:setCelsius(value)— actualiza la temperatura a un nuevo valor:getFahrenheit()— devuelve la temperatura convertida a Fahrenheit utilizando la fórmula:(celsius * 9/5) + 32
main.lua: Requiere tu módulo Temperature y lee dos valores de la entrada: una temperatura inicial y una nueva temperatura para establecer. Crea una instancia de Temperature con el valor inicial, imprime las lecturas en Celsius y Fahrenheit, luego usa el setter para cambiar la temperatura e imprime ambas lecturas nuevamente.
Recibirás dos entradas:
- La temperatura inicial en Celsius (un número)
- Una nueva temperatura para establecer (un número)
Tu salida debe constar de cuatro líneas que muestren las lecturas de temperatura antes y después del cambio:
Celsius: {initialValue}
Fahrenheit: {convertedInitial}
Celsius: {newValue}
Fahrenheit: {convertedNew}Por ejemplo, si las entradas son 0 y 100, la salida debería ser:
Celsius: 0
Fahrenheit: 32
Celsius: 100
Fahrenheit: 212Recuerda: el valor de la temperatura debe almacenarse en una variable local dentro de :new(), no en self. Los métodos getter y setter se definen dentro del constructor, donde pueden acceder a esta variable privada a través del cierre. ¡Intentar acceder a temp.celsius o temp._celsius directamente debería devolver nil!
Hoja de referencia
Para crear variables verdaderamente privadas en las clases de Lua, define métodos getter y setter dentro del constructor donde tengan acceso a las variables locales a través de un cierre (closure).
Los métodos se asignan directamente a la instancia del objeto (no a la tabla de la clase), lo que le da a cada instancia su propia copia con acceso a sus propias variables privadas:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Variable privada
local obj = {}
-- Definir métodos dentro del constructor
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endUso:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (¡sigue oculto!)Compromiso: Cada instancia obtiene su propia copia de los métodos (no compartidos a través del prototipo), lo que consume más memoria pero proporciona una verdadera privacidad.
Pruébalo tú mismo
-- Requerir el módulo Temperature
local Temperature = require('Temperature')
-- Leer los valores de entrada
local initialTemp = tonumber(io.read())
local newTemp = tonumber(io.read())
-- TODO: Crear una instancia de Temperature con el valor inicial
-- TODO: Imprimir las lecturas iniciales en Celsius y Fahrenheit
-- TODO: Usar el setter para cambiar la temperatura a newTemp
-- TODO: Imprimir las lecturas actualizadas en Celsius y Fahrenheit
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1El concepto de 'self'
Tablas con funciones'self' explícitoLa sintaxis de dos puntosPunto vs. dos puntosResumen - Punto en movimiento4Proyecto: Banco Digital
Configuración del proyectoMétodo de depósito2Patrón de prototipo de clase
El concepto de prototipoVinculación con __indexEl constructor :new()Inicialización de atributosInstancias independientesResumen - Fábrica de coches5Sobrecarga de operadores en POO
Sumar objetosRestar objetosConcatenar objetosComparar objetos (<, >)Resumen - Matemáticas de la billetera8Encapsulamiento
Convenciones de nomenclaturaClosures para privacidadAcceso mediante ClosuresTablas de solo lecturaLógica de validaciónResumen - Bóveda segura11Patrones de diseño (Lite)
Funciones FactoryTabla SingletonPatrón IteradorObserver (Listener)Resumen - Logger Factory3Estado y Comportamiento de los Objetos
Variables de InstanciaMétodos GetterMétodos SetterPropiedades CalculadasFormateo de StringsComprobaciones de IgualdadResumen - Calificación del Estudiante6Fundamentos de la herencia
Configuración de la herenciaHerencia de métodosExtender el constructorAñadir métodos hijosCompartido vs. ÚnicoResumen: Jerarquía de formas