Closures para privacidad
Parte de la sección Programación Orientada a Objetos 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—. A esto se le llama 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 ella 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 cierre (closure). Permanece completamente oculta para el código externo. (Tal como está escrito, nada captura a count todavía; en la siguiente lección añadirás métodos dentro del constructor que se cierren sobre ella, de modo que persista mientras el objeto lo haga). 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 próxima lección, aprenderás cómo definir métodos dentro del constructor que pueden leer y modificar estas variables privadas, brindándote 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 guardián (debe imprimirKeeper) - Qué sucede cuando intentas acceder a
keeper.secret(debe sernil) - Qué sucede cuando intentas acceder a
keeper._secret(también debe 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, mientras 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: nilPrué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 Programación Orientada a Objetos
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