Closures para Privacidade
Parte da seção Programação Orientada a Objetos do Journey de Lua da Coddy. Lição 44 de 70.
As convenções de nomenclatura sinalizam a intenção, mas não impedem o acesso de fato. Qualquer pessoa ainda pode escrever player._health = 999, e isso funciona. Para uma privacidade verdadeira, Lua oferece uma abordagem diferente: armazenar dados em variáveis locais dentro do construtor, em vez de armazená-los em self.
A ideia principal é que as variáveis locais dentro de uma função só podem ser acessadas dentro dessa função e de quaisquer funções definidas dentro dela. Isso é chamado de closure:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Realmente privado! Não em self.
local obj = {}
setmetatable(obj, Counter)
return obj
endNeste exemplo, count existe apenas dentro da função :new(). Ele não é armazenado em obj, então não há como acessá-lo de fora: myCounter.count retorna nil, e não há um campo prefixado com sublinhado para contornar isso.
A variável count vive no escopo da closure. Ela permanece completamente oculta do código externo. (Da forma como está escrito, nada captura count ainda; na próxima lição, você adicionará métodos dentro do construtor que criam uma closure sobre ela, fazendo com que ela persista enquanto o objeto existir.) Diferentemente de self._count, que é apenas uma sugestão educada, essa abordagem torna os dados genuinamente inacessíveis.
É claro que dados ocultos não são úteis se você nunca puder interagir com eles. Na próxima lição, você aprenderá a definir métodos dentro do construtor que podem ler e modificar essas variáveis privadas, proporcionando acesso controlado enquanto mantém o verdadeiro encapsulamento.
Desafio
FácilVamos criar uma classe SecretKeeper que demonstra a verdadeira privacidade de dados usando closures! Diferentemente da convenção do sublinhado da lição anterior, essa abordagem torna os dados genuinamente inacessíveis de fora do objeto.
Você organizará seu código em dois arquivos:
SecretKeeper.lua: crie uma classe na qual o segredo seja armazenado em uma variável local dentro do construtor, e não emself. O construtor:new(secretValue)deve armazenar o segredo em uma variável local que exista apenas dentro da closure. Por enquanto, o objeto não terá nenhuma forma de acessar esse segredo (isso será apresentado na próxima lição!), mas você também deve armazenar um campo públiconamedefinido como"Keeper"na instância, para que possamos verificar se o objeto funciona.main.lua: exija seu módulo SecretKeeper e leia um valor secreto da entrada. Crie uma instância de SecretKeeper com esse segredo. Em seguida, demonstre que o segredo é realmente privado imprimindo três coisas:- O campo
namedo keeper (deve imprimirKeeper) - O que acontece quando você tenta acessar
keeper.secret(deve sernil) - O que acontece quando você tenta acessar
keeper._secret(também deve sernil)
- O campo
Você receberá uma entrada:
- O valor secreto a ser armazenado (por exemplo,
TopSecret123)
Sua saída deve conter três linhas mostrando que, embora o campo público funcione, o segredo está completamente oculto:
Name: Keeper
secret: nil
_secret: nilA saída será a mesma independentemente do segredo que você fornecer, pois não há como acessá-lo! A variável local dentro do construtor é invisível para o mundo exterior. Isso prova que a privacidade baseada em closures é fundamentalmente diferente da convenção de nomenclatura com sublinhado.
Por exemplo, se a entrada for MyPassword, a saída ainda deverá ser:
Name: Keeper
secret: nil
_secret: nilExperimente você mesmo
-- Requer o módulo SecretKeeper
local SecretKeeper = require('SecretKeeper')
-- Lê o valor secreto da entrada
local secretValue = io.read()
-- TODO: Crie uma instância de SecretKeeper com o valor secreto
-- TODO: Imprima o campo name do keeper (formato: "Name: <name>")
-- TODO: Imprima o que acontece ao acessar keeper.secret (formato: "secret: <value>")
-- TODO: Imprima o que acontece ao acessar keeper._secret (formato: "_secret: <value>")
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1O Conceito 'Self'
Tabelas com Funções'self' ExplícitoA Sintaxe de Dois-pontosPonto vs Dois-pontosRecapitulação - Movendo o Ponto4Projeto: Banco Digital
Configuração do ProjetoMétodo de Depósito7Polimorfismo e Sobrescrita
Sobrescrita de MétodosChamando Métodos da Classe PaiDuck TypingInterface ComumVerificando o TipoRecapitulação - Papéis de Funcionários10Projeto: Gerenciador de Formas
Configuração do ProjetoClasse RetânguloClasse CírculoMétodo PerímetroColeção de FormasÁrea TotalFiltrar Formas2Padrão de Protótipo de Classe
O Conceito de ProtótipoVinculando com __indexO Construtor :new()Inicializando AtributosInstâncias IndependentesRecapitulação - Fábrica de Carros5Sobrecarga de Operadores em POO
Somando ObjetosSubtraindo ObjetosConcatenando ObjetosComparando Objetos (<, >)Recapitulação - Matemática de Carteira8Encapsulamento
Convenções de NomenclaturaClosures para PrivacidadeAcesso via ClosuresTabelas Somente LeituraLógica de ValidaçãoRecapitulação - Cofre Seguro11Padrões de Projeto (Lite)
Funções FactoryTabela SingletonPadrão IteratorObserver (Listener)Recapitulação - Logger Factory3Estado e Comportamento do Objeto
Variáveis de InstânciaMétodos GetterMétodos SetterPropriedades CalculadasFormatação de StringsVerificações de IgualdadeRecapitulação - Nota do Aluno6Fundamentos de Herança
Configurando a HerançaHerdando MétodosEstendendo o ConstrutorAdicionando Métodos FilhosCompartilhado vs ÚnicoRecapitulação - Hierarquia de FormasPratique por conta própria: Compilador de Lua online