Acesso via Closures
Parte da seção Object Oriented Programming do Journey de Lua da Coddy — lição 45 de 70.
Variáveis locais privadas são inúteis se você nunca puder interagir com elas. A solução é definir métodos getter e setter dentro do construtor—onde eles têm acesso ao escopo da closure.
Como esses métodos são criados dentro de :new(), eles podem "ver" as variáveis locais. Você os atribui diretamente ao objeto:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Variável privada
local obj = {}
-- Define métodos dentro do construtor
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endAgora a variável privada count pode ser acessada — mas apenas através dos métodos que você fornece:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (ainda oculto!)Observe que :getCount() e :increment() são definidos diretamente em obj, não na tabela da classe. Cada instância recebe sua própria cópia dessas funções, cada uma com acesso ao seu próprio count privado. Este é o trade-off: a verdadeira privacidade custa um pouco mais de memória, já que os métodos não são compartilhados através do protótipo.
Desafio
FácilVamos construir uma classe Temperature que utiliza privacidade baseada em fechamento (closure) para armazenar um valor de temperatura em Celsius, com métodos getter e setter definidos dentro do construtor para fornecer acesso controlado!
Você organizará seu código em dois arquivos:
Temperature.lua: Crie uma classe onde o valor da temperatura é armazenado em uma variável local dentro do construtor — completamente oculta do acesso externo. O construtor:new(initialCelsius)deve armazenar a temperatura em uma variável local e, em seguida, definir estes métodos diretamente na instância do objeto::getCelsius()— retorna a temperatura atual em Celsius:setCelsius(value)— atualiza a temperatura para um novo valor:getFahrenheit()— retorna a temperatura convertida para Fahrenheit usando a fórmula:(celsius * 9/5) + 32
main.lua: Requeira seu módulo Temperature e leia dois valores da entrada: uma temperatura inicial e uma nova temperatura para definir. Crie uma instância de Temperature com o valor inicial, imprima as leituras em Celsius e Fahrenheit, use o setter para alterar a temperatura e imprima ambas as leituras novamente.
Você receberá duas entradas:
- A temperatura inicial em Celsius (um número)
- Uma nova temperatura para definir (um número)
Sua saída deve ter quatro linhas mostrando as leituras de temperatura antes e depois da alteração:
Celsius: {initialValue}
Fahrenheit: {convertedInitial}
Celsius: {newValue}
Fahrenheit: {convertedNew}Por exemplo, se as entradas forem 0 e 100, a saída deve ser:
Celsius: 0
Fahrenheit: 32
Celsius: 100
Fahrenheit: 212Lembre-se: o valor da temperatura deve ser armazenado em uma variável local dentro de :new(), não em self. Os métodos getter e setter são definidos dentro do construtor, onde podem acessar essa variável privada através de closure. Tentar acessar temp.celsius ou temp._celsius diretamente deve retornar nil!
Folha de consulta
Para criar variáveis verdadeiramente privadas em classes Lua, defina métodos getter e setter dentro do construtor, onde eles têm acesso a variáveis locais por meio de closure.
Os métodos são atribuídos diretamente à instância do objeto (não à tabela da classe), dando a cada instância sua própria cópia com acesso às suas próprias variáveis privadas:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Private variable
local obj = {}
-- Define methods inside the 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 (still hidden!)Compensação (Trade-off): Cada instância recebe sua própria cópia dos métodos (não compartilhada através do protótipo), o que consome mais memória, mas oferece privacidade real.
Experimente você mesmo
-- Importa o módulo Temperature
local Temperature = require('Temperature')
-- Lê os valores de entrada
local initialTemp = tonumber(io.read())
local newTemp = tonumber(io.read())
-- TODO: Cria uma instância de Temperature com o valor inicial
-- TODO: Imprime as leituras iniciais em Celsius e Fahrenheit
-- TODO: Usa o setter para alterar a temperatura para newTemp
-- TODO: Imprime as leituras atualizadas em Celsius e Fahrenheit
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Object Oriented Programming
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ângulo2Padrã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 Formas