読み取り専用テーブル
CoddyのLuaジャーニー「Object Oriented Programming」セクションの一部 — レッスン 46/70。
クロージャは真のプライバシーを提供しますが、コンストラクタ内でメソッドを定義する必要があります。別のユースケースのための別のアプローチがあります。それは、作成後にオブジェクトを読み取り専用にすることです。これは、固定点の座標のように、フィールドを可視にしたいが変更はしたくない場合に便利です。
__newindex メタメソッドは、テーブルに新しい値を代入しようとする試みをインターセプトします。すべての代入をブロックするように設定することで、オブジェクトを凍結させることができます:
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
endコツは、プロキシテーブルを使用することです。実際のデータはdataに格納されており、ユーザーは空のproxyを操作します。
読み取り時、__index は data から取得します。書き込み時は、代わりに __newindex がトリガーされ、それをブロックします:
local p = ImmutablePoint.new(10, 20)
print(p.x) -- 10 (読み取りは可能)
p.x = 50 -- エラー: 不変オブジェクトは変更できませんこのパターンは、設定オブジェクト、数学定数、または初期化後に変更すべきではないあらゆるデータに役立ちます。オブジェクトは完全に読み取り可能なままですが、変更することはできません。
チャレンジ
簡単読み取り専用の設定オブジェクトを作成する ImmutableConfig クラスを構築しましょう!一度作成されたこれらの設定オブジェクトは、設定の読み取りは許可しますが、変更の試みは完全にブロックする必要があります。
コードは2つのファイルに分けて構成します:
ImmutableConfig.lua: 凍結された設定オブジェクトを生成するモジュールを作成します。.new(appName, version, maxUsers)関数は、これら3つの値を内部的に保存し、すべてのフィールドの読み取りを許可しつつ変更を防止するプロキシテーブルを返す必要があります。誰かが値を変更しようとしたときは、実際の例外を発生させる代わりに"Error: Config is read-only"と出力するようにしてください。main.lua: ImmutableConfig を実際に動かしてみましょう!設定値として3つの入力を読み込み、設定オブジェクトを作成します。その後、以下の手順で正しく動作することを示してください:- 3つの元の値をすべて出力する
maxUsersフィールドの変更を試みる(これによりエラーメッセージが表示されるはずです)maxUsersを再度出力し、実際に変更されていないことを証明する
以下の3つの入力を受け取ります:
- アプリケーション名(文字列)
- バージョン(文字列)
- 最大ユーザー数(数値)
出力には、設定値、変更を試みた際のエラー、および値が変更されずに維持されていることの確認を表示する必要があります:
App: {appName}
Version: {version}
Max Users: {maxUsers}
Error: Config is read-only
Max Users: {maxUsers}例えば、入力が "MyApp"、"2.0"、100 の場合、出力は以下のようになります:
App: MyApp
Version: 2.0
Max Users: 100
Error: Config is read-only
Max Users: 100ヒント:プロキシパターンでは、ユーザーが操作する空のテーブルを使用し、実際のデータは別の内部テーブルに保持します。__index メタメソッドはデータテーブルから値を取得し、__newindex はすべての書き込みの試みをインターセプト(およびブロック)します!
チートシート
__newindex メタメソッドは、テーブルに新しい値を代入しようとする試みをインターセプト(遮断)します。すべての代入をブロックすることで、イミュータブル(読み取り専用)なオブジェクトを作成できます:
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
endプロキシパターンは、ユーザーが操作する空のテーブル(proxy)を使用し、実際のデータは別の内部テーブル(data)に保持されます:
__indexは、読み取り時にdataテーブルから値を取得します__newindexは、書き込みの試みをインターセプトし、それらをブロックします
local p = ImmutablePoint.new(10, 20)
print(p.x) -- 10 (reading works)
p.x = 50 -- Error: Cannot modify immutable objectこのパターンは、設定オブジェクト、数学定数、または初期化後に変更すべきではないあらゆるデータに役立ちます。
自分で試してみよう
-- ImmutableConfigモジュールをインポートする
local ImmutableConfig = require('ImmutableConfig')
-- 入力を読み込む
local appName = io.read()
local version = io.read()
local maxUsers = tonumber(io.read())
-- TODO: ImmutableConfig.new()を使用して設定オブジェクトを作成する
-- TODO: 次の形式で3つの元の値をすべて出力する:
-- App: {appName}
-- Version: {version}
-- Max Users: {maxUsers}
-- TODO: maxUsersフィールドの変更を試みる(これによりエラーメッセージが表示されるはずです)
-- TODO: 変更されていないことを証明するために、再度maxUsersを出力する
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。