Tabelas Somente Leitura
Parte da seção Object Oriented Programming do Journey de Lua da Coddy — lição 46 de 70.
Closures provêem privacidade real, mas exigem a definição de métodos dentro do construtor. Existe outra abordagem para um caso de uso diferente: tornar um objeto somente leitura após a criação. Isso é útil quando você deseja que os campos sejam visíveis, mas imutáveis — como as coordenadas de um ponto fixo.
O metamétodo __newindex intercepta tentativas de atribuir novos valores a uma tabela. Ao fazer com que ele bloqueie todas as atribuições, você pode congelar um objeto:
local ImmutablePoint = {}
function ImmutablePoint.new(x, y)
local data = {x = x, y = y}
local proxy = {}
setmetatable(proxy, {
__index = data,
__newindex = function()
error("Cannot modify immutable object")
end
})
return proxy
endO truque é usar uma tabela proxy. Os dados reais residem em data, enquanto o usuário interage com o proxy vazio.
Ao ler, __index busca de data. Ao escrever, __newindex é acionado em vez disso — e nós o bloqueamos:
local p = ImmutablePoint.new(10, 20)
print(p.x) -- 10 (leitura funciona)
p.x = 50 -- Erro: Não é possível modificar um objeto imutávelEste padrão é valioso para objetos de configuração, constantes matemáticas ou quaisquer dados que nunca devam mudar após a inicialização. O objeto permanece totalmente legível — você apenas não pode alterá-lo.
Desafio
FácilVamos construir uma classe ImmutableConfig que cria objetos de configuração somente leitura! Uma vez criados, esses objetos de configuração devem permitir a leitura de suas definições, mas bloquear completamente qualquer tentativa de modificá-los.
Você organizará seu código em dois arquivos:
ImmutableConfig.lua: Crie um módulo que produz objetos de configuração congelados. Sua função.new(appName, version, maxUsers)deve armazenar esses três valores internamente e, em seguida, retornar uma tabela proxy que permite a leitura de todos os campos, mas impede qualquer modificação. Quando alguém tentar alterar um valor, deve imprimirError: Config is read-onlyem vez de gerar um erro real.main.lua: Dê vida ao seu ImmutableConfig! Leia três entradas para os valores de configuração, crie um objeto de configuração e, em seguida, demonstre que ele funciona corretamente:- Imprimindo todos os três valores originais
- Tentando modificar o campo
maxUsers(o que deve disparar a mensagem de erro) - Imprimindo
maxUsersnovamente para provar que ele não foi alterado de fato
Você receberá três entradas:
- O nome do aplicativo (uma string)
- A versão (uma string)
- O número máximo de usuários (um número)
Sua saída deve mostrar os valores de configuração, o erro quando a modificação é tentada e a confirmação de que o valor permaneceu inalterado:
App: {appName}
Version: {version}
Max Users: {maxUsers}
Error: Config is read-only
Max Users: {maxUsers}Por exemplo, se as entradas forem MyApp, 2.0 e 100, a saída deve ser:
App: MyApp
Version: 2.0
Max Users: 100
Error: Config is read-only
Max Users: 100Lembre-se: o padrão proxy usa uma tabela vazia com a qual o usuário interage, enquanto os dados reais vivem em uma tabela interna separada. O metamétodo __index busca os dados da tabela interna, e o __newindex intercepta (e bloqueia) qualquer tentativa de escrita!
Folha de consulta
O metamétodo __newindex intercepta tentativas de atribuir novos valores a uma tabela. Ao bloquear todas as atribuições, você pode criar objetos imutáveis (somente leitura):
local ImmutablePoint = {}
function ImmutablePoint.new(x, y)
local data = {x = x, y = y}
local proxy = {}
setmetatable(proxy, {
__index = data,
__newindex = function()
error("Cannot modify immutable object")
end
})
return proxy
endO padrão proxy usa uma tabela vazia (proxy) com a qual o usuário interage, enquanto os dados reais residem em uma tabela interna separada (data):
__indexbusca valores da tabeladataao ler__newindexintercepta tentativas de escrita e as bloqueia
local p = ImmutablePoint.new(10, 20)
print(p.x) -- 10 (reading works)
p.x = 50 -- Error: Cannot modify immutable objectEste padrão é útil para objetos de configuração, constantes matemáticas ou quaisquer dados que devam permanecer inalterados após a inicialização.
Experimente você mesmo
-- Importa o módulo ImmutableConfig
local ImmutableConfig = require('ImmutableConfig')
-- Lê as entradas
local appName = io.read()
local version = io.read()
local maxUsers = tonumber(io.read())
-- TODO: Cria um objeto de configuração usando ImmutableConfig.new()
-- TODO: Imprime todos os três valores originais no formato:
-- App: {appName}
-- Versão: {version}
-- Máximo de Usuários: {maxUsers}
-- TODO: Tenta modificar o campo maxUsers (isso deve disparar uma mensagem de erro)
-- TODO: Imprime maxUsers novamente para provar que não foi alterado
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