Tabela Singleton
Parte da seção Programação Orientada a Objetos do Journey de Lua da Coddy — lição 63 de 70.
Às vezes, você precisa de exatamente uma instância de algo em todo o seu programa — um gerenciador de configuração, um sistema de log ou configurações de jogo. Criar múltiplas cópias causaria problemas: qual delas possui os dados corretos? O padrão Singleton garante que apenas uma instância exista.
Em Lua, implementar um Singleton é surpreendentemente simples graças à forma como os módulos funcionam. Quando você usa require em um módulo, o Lua faz o cache do resultado. Chamadas subsequentes para require retornam a tabela em cache, não uma nova.
-- GameConfig.lua
local GameConfig = {
volume = 80,
difficulty = "normal"
}
function GameConfig:setVolume(v)
self.volume = v
end
return GameConfig
Agora, em qualquer lugar do seu programa:
local config1 = require("GameConfig")
local config2 = require("GameConfig")
config1:setVolume(50)
print(config2.volume) -- 50 (mesma tabela!)
Tanto config1 quanto config2 referenciam exatamente a mesma tabela. Alterações feitas através de uma variável são visíveis através da outra. Isso acontece automaticamente porque o require do Lua armazena o valor retornado em package.loaded e o reutiliza.
Ao contrário do padrão Factory, que cria novos objetos, um Singleton garante um estado compartilhado em toda a sua aplicação. Isso é ideal para configurações globais que múltiplas partes do seu código precisam acessar e modificar de forma consistente.
Desafio
FácilVamos construir um gerenciador de configurações de jogo usando o padrão Singleton! Você criará um módulo de configuração que garante que cada parte do seu programa acesse exatamente as mesmas configurações — não importa quantas vezes ele seja requisitado.
Você organizará seu código em dois arquivos:
GameSettings.lua: Crie um módulo Singleton que armazena a configuração do jogo. Sua tabela de configurações deve incluirmusicVolume(começando em 70),sfxVolume(começando em 100) edifficulty(começando em"normal"). Adicione três métodos::setMusicVolume(value),:setSfxVolume(value)e:setDifficulty(level)para modificar essas configurações. Lembre-se de retornar a tabela ao final do módulo!main.lua: Demonstre que o Singleton funciona requisitando seu módulo GameSettings duas vezes em variáveis diferentes. Use a primeira variável para alterar o volume da música e a dificuldade. Em seguida, use a segunda variável para imprimir todas as três configurações — se o Singleton estiver funcionando corretamente, ambas as variáveis referenciam a mesma tabela, portanto, as alterações feitas por uma são visíveis pela outra.
Você receberá duas entradas:
- Novo volume de música (um número)
- Novo nível de dificuldade (uma string como
"easy"ou"hard")
Após modificar as configurações através da primeira variável, imprima as três configurações usando a segunda variável, cada uma em sua própria linha nesta ordem: volume da música, volume de sfx e, em seguida, dificuldade.
Por exemplo, se as entradas forem 50 e hard, a saída deve ser:
50
100
hardSe as entradas forem 25 e easy, a saída deve ser:
25
100
easyObserve que o volume de sfx permanece em seu valor padrão de 100, pois nunca o alteramos — mas o volume da música e a dificuldade refletem as alterações feitas por meio da primeira variável, provando que ambas as variáveis apontam para a mesma instância Singleton!
Experimente você mesmo
-- main.lua - Demonstra o padrão Singleton
-- Lê as entradas
local newMusicVolume = tonumber(io.read())
local newDifficulty = io.read()
-- TODO: Importar o módulo GameSettings para uma variável (ex: settings1)
-- TODO: Importar o módulo GameSettings NOVAMENTE para uma variável DIFERENTE (ex: settings2)
-- (Se o Singleton funcionar corretamente, ambas as variáveis referenciarão a mesma tabela!)
-- TODO: Use a PRIMEIRA variável para:
-- - Definir o volume da música para newMusicVolume
-- - Definir a dificuldade para newDifficulty
-- TODO: Use a SEGUNDA variável para imprimir as configurações
-- Imprima cada um em sua própria linha: musicVolume, sfxVolume, difficulty
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 Formas