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 indican la intención, pero en realidad no impiden el acceso. Cualquiera puede escribir player._health = 999 y funciona. Para una privacidad verdadera, Lua ofrece un enfoque diferente: almacenar los datos en variables locales dentro del constructor en lugar de hacerlo 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 en su interior. Esto se denomina una clausura:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- ¡Verdaderamente privado! No en self.
local obj = {}
setmetatable(obj, Counter)
return obj
endEn este ejemplo, count existe únicamente dentro de la función :new(). No se almacena en obj, por lo que no hay forma de acceder a él desde fuera: myCounter.count devuelve nil, y no existe ningún campo con prefijo de guion bajo que permita omitir esta restricción.
La variable count vive en el ámbito léxico del cierre. Permanece completamente oculta para el código externo. (Tal como está escrito, nada captura count todavía; en la próxima lección añadirás métodos dentro del constructor que creen un cierre sobre ella, por lo que persistirá mientras exista el objeto.) A diferencia de self._count, que es solo una sugerencia cortés, este enfoque hace que los datos sean realmente inaccesibles.
Por supuesto, los datos ocultos no son útiles si nunca puedes interactuar con ellos. En la próxima lección, aprenderás a definir métodos dentro del constructor que pueden leer y modificar estas variables privadas, lo que te proporciona un acceso controlado mientras mantienes una verdadera encapsulación.
Desafío
Fácil¡Construyamos una clase SecretKeeper que demuestre una verdadera privacidad de datos mediante cierres! A diferencia de la convención del guion bajo de la lección anterior, este enfoque hace que los datos sean realmente inaccesibles desde fuera del objeto.
Organizarás tu código en dos archivos:
SecretKeeper.lua: Crea una clase en la que 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 únicamente dentro del cierre. Por ahora, el objeto no tendrá ninguna forma de acceder a este secreto (¡eso llegará en la próxima lección!), pero también debes almacenar un campo públiconamecon el valor"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. Después, demuestra que el secreto es realmente privado imprimiendo tres cosas:- El campo
namedel guardián (debería imprimirKeeper) - Qué ocurre cuando intentas acceder a
keeper.secret(debería sernil) - Qué ocurre cuando intentas acceder a
keeper._secret(también debería sernil)
- El campo
Recibirás una entrada:
- El valor secreto que se almacenará (por ejemplo,
TopSecret123)
Tu salida debe mostrar tres líneas que demuestren que, aunque el campo público funciona, el secreto está completamente oculto:
Name: Keeper
secret: nil
_secret: nilLa salida será la misma independientemente del secreto que introduzcas, ¡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 nombres con guion bajo.
Por ejemplo, si la entrada es MyPassword, la salida seguirá siendo:
Name: Keeper
secret: nil
_secret: nilPruébalo tú mismo
-- Requiere el módulo SecretKeeper
local SecretKeeper = require('SecretKeeper')
-- Lee el valor secreto de la entrada
local secretValue = io.read()
-- TODO: Crea una instancia de SecretKeeper con el valor secreto
-- TODO: Imprime el campo name del keeper (formato: "Name: <name>")
-- TODO: Imprime lo que sucede al acceder a keeper.secret (formato: "secret: <value>")
-- TODO: Imprime lo que 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 formasPractica por tu cuenta: Compilador de Lua online