Closures para privacidad
Parte de la sección Object Oriented Programming del Journey de Lua de Coddy — lección 44 de 70.
Las convenciones de nomenclatura señalan la intención, pero en realidad no impiden el acceso. Cualquiera puede seguir escribiendo player._health = 999 y funciona. Para una privacidad real, Lua ofrece un enfoque diferente: almacenar datos en variables locales dentro del constructor en lugar de en self.
La idea clave es que las variables locales dentro de una función solo son accesibles dentro de esa función —y de cualquier función definida dentro de ella—. Esto se llama una clausura (closure):
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- ¡Verdaderamente privado! No está en self.
local obj = {}
setmetatable(obj, Counter)
return obj
endEn este ejemplo, count existe solo dentro de la función :new(). No se almacena en obj, por lo que no hay forma de acceder a él desde el exterior—myCounter.count devuelve nil, y no hay ningún campo con prefijo de guion bajo para omitir.
La variable count vive en el ámbito del closure. Persiste mientras el objeto exista, pero está completamente oculta para el código externo. A diferencia de self._count, que es solo una sugerencia de cortesía, este enfoque hace que los datos sean genuinamente inaccesibles.
Por supuesto, los datos ocultos no son útiles si nunca puedes interactuar con ellos. En la siguiente lección, aprenderás a definir métodos dentro del constructor que pueden leer y modificar estas variables privadas, lo que te permitirá tener un acceso controlado mientras mantienes una verdadera encapsulación.
Desafío
Fácil¡Vamos a construir una clase SecretKeeper que demuestre la verdadera privacidad de datos utilizando cierres (closures)! A diferencia de la convención del guion bajo de la lección anterior, este enfoque hace que los datos sean genuinamente inaccesibles desde fuera del objeto.
Organizarás tu código en dos archivos:
SecretKeeper.lua: Crea una clase donde el secreto se almacene en una variable local dentro del constructor, no enself. El constructor:new(secretValue)debe almacenar el secreto en una variable local que exista solo dentro del cierre. Por ahora, el objeto no tendrá ninguna forma de acceder a este secreto (¡eso vendrá en la próxima lección!), pero también debes almacenar un campo públiconameestablecido en"Keeper"en la instancia para que podamos verificar que el objeto funciona.main.lua: Requiere tu módulo SecretKeeper y lee un valor secreto de la entrada. Crea una instancia de SecretKeeper con ese secreto. Luego, demuestra que el secreto es verdaderamente privado imprimiendo tres cosas:- El campo
namedel keeper (debería imprimirKeeper) - Qué sucede cuando intentas acceder a
keeper.secret(debería sernil) - Qué sucede cuando intentas acceder a
keeper._secret(también debería sernil)
- El campo
Recibirás una entrada:
- El valor secreto a almacenar (por ejemplo,
TopSecret123)
Tu salida debe constar de tres líneas que muestren que, si bien el campo público funciona, el secreto está completamente oculto:
Name: Keeper
secret: nil
_secret: nilLa salida será la misma independientemente del secreto que pases, ¡porque no hay forma de acceder a él! La variable local dentro del constructor es invisible para el mundo exterior. Esto demuestra que la privacidad basada en cierres es fundamentalmente diferente de la convención de nomenclatura con guion bajo.
Por ejemplo, si la entrada es MyPassword, la salida aún debería ser:
Name: Keeper
secret: nil
_secret: nilHoja de referencia
Para crear datos verdaderamente privados en Lua, almacénelos en variables locales dentro del constructor en lugar de en self. Esto utiliza un closure: las variables locales solo son accesibles dentro de la función donde se definen y en cualquier función definida dentro de ella.
Ejemplo de una clase con datos privados:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- ¡Verdaderamente privado! No está en self.
local obj = {}
setmetatable(obj, Counter)
return obj
endEn este ejemplo, count existe solo dentro de la función :new(). No se almacena en obj, por lo que myCounter.count devuelve nil. No hay forma de acceder a él desde el exterior; a diferencia de self._count (que es solo una convención de nomenclatura), este enfoque hace que los datos sean genuinamente inaccesibles.
La variable persiste mientras el objeto exista, pero permanece completamente oculta para el código externo, proporcionando una verdadera encapsulación.
Pruébalo tú mismo
-- Requerir el módulo SecretKeeper
local SecretKeeper = require('SecretKeeper')
-- Leer el valor secreto de la entrada
local secretValue = io.read()
-- TODO: Crear una instancia de SecretKeeper con el valor secreto
-- TODO: Imprimir el campo name del keeper (formato: "Name: <name>")
-- TODO: Imprimir qué sucede al acceder a keeper.secret (formato: "secret: <value>")
-- TODO: Imprimir qué sucede al acceder a keeper._secret (formato: "_secret: <value>")
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