Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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 incluir musicVolume (começando em 70), sfxVolume (começando em 100) e difficulty (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:

  1. Novo volume de música (um número)
  2. 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
hard

Se as entradas forem 25 e easy, a saída deve ser:

25
100
easy

Observe 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
quiz iconTeste seus conhecimentos

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