Tabela Singleton
Parte da seção Object Oriented Programming 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ções, 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ê faz o require de um módulo, o Lua armazena o resultado em cache. 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!)
Ambos config1 e 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 várias 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, já que nunca o alteramos — mas o volume da música e a dificuldade refletem as alterações feitas através da primeira variável, provando que ambas as variáveis apontam para a mesma instância Singleton!
Folha de consulta
O padrão Singleton garante que apenas uma instância de um objeto exista em todo o seu programa. Isso é útil para gerenciadores de configuração, sistemas de log ou configurações de jogo onde múltiplas instâncias causariam inconsistência de dados.
Em Lua, o padrão Singleton é implementado naturalmente através do sistema de módulos. Quando você usa require em um módulo, Lua armazena o resultado em cache em package.loaded. Chamadas subsequentes para require retornam a mesma tabela em cache, não uma nova.
Criando um módulo Singleton:
-- GameConfig.lua
local GameConfig = {
volume = 80,
difficulty = "normal"
}
function GameConfig:setVolume(v)
self.volume = v
end
return GameConfig
Usando o Singleton:
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 imediatamente visíveis através da outra, garantindo um estado compartilhado em toda a sua aplicação.
O padrão Singleton difere do padrão Factory: enquanto o Factory cria novos objetos, o Singleton garante uma única instância compartilhada com estado consistente.
Experimente você mesmo
-- main.lua - Demonstra o padrão Singleton
-- Ler entradas
local newMusicVolume = tonumber(io.read())
local newDifficulty = io.read()
-- TODO: Requerer o módulo GameSettings em uma variável (ex: settings1)
-- TODO: Requerer o módulo GameSettings NOVAMENTE em uma variável DIFERENTE (ex: settings2)
-- (Se o Singleton funcionar corretamente, ambas as variáveis referenciam a mesma tabela!)
-- TODO: Usar a PRIMEIRA variável para:
-- - Definir o volume da música para newMusicVolume
-- - Definir a dificuldade para newDifficulty
-- TODO: Usar a SEGUNDA variável para imprimir as configurações
-- Imprimir cada uma 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 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