シングルトンテーブル
CoddyのLuaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 63/70。
プログラム全体で、設定マネージャー、ロギングシステム、ゲーム設定など、あるもののインスタンスが1つだけ必要になることがあります。複数のコピーを作成すると、どれが正しいデータを持っているのかという問題が生じます。Singletonパターンは、インスタンスが1つだけ存在することを保証します。
Luaでは、モジュールの仕組みのおかげでSingletonの実装は驚くほど簡単です。モジュールをrequireすると、Luaはその結果をキャッシュします。その後のrequireの呼び出しでは、新しいテーブルではなく、キャッシュされたテーブルが返されます。
-- GameConfig.lua
local GameConfig = {
volume = 80,
difficulty = "normal"
}
function GameConfig:setVolume(v)
self.volume = v
end
return GameConfig
これで、プログラム内のどこでも次のようにできます。
local config1 = require("GameConfig")
local config2 = require("GameConfig")
config1:setVolume(50)
print(config2.volume) -- 50 (同じテーブル!)
config1 と config2 は、まったく同じテーブルを参照しています。一方の変数を通じて行った変更は、もう一方からも確認できます。これは、Lua の require が返された値を package.loaded に保存し、それを再利用するため、自動的に発生します。
新しいオブジェクトを作成する Factory パターンとは異なり、Singleton はアプリケーション全体で共有される状態を保証します。これは、コードの複数の部分から一貫してアクセスおよび変更する必要があるグローバル設定に最適です。
チャレンジ
簡単Singletonパターンを使って、ゲーム設定マネージャーを作成しましょう!プログラムのどの部分からアクセスしても、何回requireしても常にまったく同じ設定にアクセスできる構成モジュールを作成します。
コードを2つのファイルに分けて整理します。
GameSettings.lua:ゲーム設定を保存するSingletonモジュールを作成します。設定テーブルには、musicVolume(初期値は70)、sfxVolume(初期値は100)、difficulty(初期値は"normal")を含めます。これらの設定を変更するために、:setMusicVolume(value)、:setSfxVolume(value)、:setDifficulty(level)の3つのメソッドを追加します。モジュールの最後でテーブルをreturnすることを忘れないでください。main.lua:GameSettingsモジュールを異なる変数に2回requireして、Singletonが機能することを示します。最初の変数を使って音量と難易度を変更します。その後、2番目の変数を使って3つすべての設定を出力します。Singletonが正しく機能していれば、両方の変数は同じテーブルを参照するため、一方を通して行った変更がもう一方からも確認できます。
入力は2つ与えられます。
- 新しい音量(数値)
- 新しい難易度(
"easy"や"hard"のような文字列)
最初の変数を通して設定を変更した後、2番目の変数を使って3つの設定を出力します。それぞれ1行に1つずつ、順番は音量、sfx音量、難易度です。
例えば、入力が50とhardの場合、出力は次のようになります。
50
100
hard入力が25とeasyの場合、出力は次のようになります。
25
100
easysfx音量は変更していないため初期値の100のままですが、音量と難易度には最初の変数を通して行った変更が反映されていることに注目してください。これは、両方の変数が同じSingletonインスタンスを指していることを証明しています。
自分で試してみよう
-- main.lua - Singletonパターンを実演する
-- 入力を読み取る
local newMusicVolume = tonumber(io.read())
local newDifficulty = io.read()
-- TODO: GameSettingsモジュールを変数にrequireする(例: settings1)
-- TODO: GameSettingsモジュールを再度、別の変数にrequireする(例: settings2)
-- (Singletonが正しく動作していれば、両方の変数は同じテーブルを参照します!)
-- TODO: 最初の変数を使って:
-- - 音楽ボリュームをnewMusicVolumeに設定する
-- - 難易度をnewDifficultyに設定する
-- TODO: 2番目の変数を使って設定を出力する
-- それぞれを独自の行に出力する: musicVolume, sfxVolume, difficulty
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Luaオンラインコンパイラ