クロージャを介したアクセス
CoddyのLuaジャーニー「Object Oriented Programming」セクションの一部 — レッスン 45/70。
プライベートなローカル変数は、それらを操作することができなければ役に立ちません。解決策は、コンストラクタの内部でゲッター(getter)およびセッター(setter)メソッドを定義することです。そこでは、それらのメソッドがクロージャのスコープにアクセスできます。
これらのメソッドは :new() の内部で作成されるため、ローカル変数を「参照」することができます。それらをオブジェクトに直接割り当てます:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- プライベート変数
local obj = {}
-- コンストラクタ内でメソッドを定義する
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
endこれで、プライベートな count 変数にアクセスできるようになりますが、それは提供されたメソッドを通じてのみ可能です:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (まだ隠されています!):getCount() と :increment() は、クラス・テーブルではなく obj に直接定義されていることに注目してください。各インスタンスはこれらの関数の独自のコピーを保持し、それぞれが独自のプライベートな count にアクセスできます。これがトレードオフです。メソッドがプロトタイプを介して共有されないため、真のプライバシーを実現するにはメモリ消費が少し増えます。
チャレンジ
簡単クロージャベースのプライバシーを使用して摂氏の温度値を保存する Temperature クラスを作成しましょう。コンストラクタ内でゲッターとセッターメソッドを定義することで、制御されたアクセスを提供します。
コードは2つのファイルに分けて構成します:
Temperature.lua: 温度値をコンストラクタ内のローカル変数に保存し、外部からのアクセスを完全に隠蔽するクラスを作成します。コンストラクタ:new(initialCelsius)は温度をローカル変数に保存し、以下のメソッドをオブジェクトインスタンスに直接定義する必要があります::getCelsius()— 現在の摂氏温度を返します:setCelsius(value)— 温度を新しい値に更新します:getFahrenheit()— 次の公式を使用して、温度を華氏に変換して返します:(celsius * 9/5) + 32
main.lua: Temperature モジュールを require し、入力から2つの値(初期温度と新しく設定する温度)を読み取ります。初期値で Temperature インスタンスを作成し、摂氏と華氏の読み取り値を出力します。その後、セッターを使用して温度を変更し、両方の読み取り値を再度出力します。
2つの入力を受け取ります:
- 初期の摂氏温度(数値)
- 新しく設定する温度(数値)
出力は、変更前後の温度の読み取り値を示す4行である必要があります:
Celsius: {initialValue}
Fahrenheit: {convertedInitial}
Celsius: {newValue}
Fahrenheit: {convertedNew}例えば、入力が 0 と 100 の場合、出力は以下のようになります:
Celsius: 0
Fahrenheit: 32
Celsius: 100
Fahrenheit: 212注意:温度値は self ではなく、:new() 内のローカル変数に保存する必要があります。ゲッターとセッターメソッドは、クロージャを通じてこのプライベート変数にアクセスできるコンストラクタ内で定義されます。temp.celsius や temp._celsius に直接アクセスしようとすると nil が返されるはずです!
チートシート
Luaのクラスで真にプライベートな変数を作成するには、クロージャを通じてローカル変数にアクセスできるコンストラクタ内でゲッターおよびセッターメソッドを定義します。
メソッドは(クラステーブルではなく)オブジェクトインスタンスに直接割り当てられ、各インスタンスが独自のプライベート変数にアクセスできる独自のコピーを持つようになります:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- プライベート変数
local obj = {}
-- コンストラクタ内でメソッドを定義する
function obj:getCount()
return count
end
function obj:increment()
count = count + 1
end
setmetatable(obj, Counter)
return obj
end使い方:
local c = Counter:new()
print(c:getCount()) -- 0
c:increment()
c:increment()
print(c:getCount()) -- 2
print(c.count) -- nil (まだ隠されています!)トレードオフ: 各インスタンスは(プロトタイプを通じて共有されるのではなく)メソッドの独自のコピーを取得するため、より多くのメモリを消費しますが、真のプライバシーを提供します。
自分で試してみよう
-- Temperatureモジュールを読み込む
local Temperature = require('Temperature')
-- 入力値を読み込む
local initialTemp = tonumber(io.read())
local newTemp = tonumber(io.read())
-- TODO: 初期値を使用してTemperatureインスタンスを作成する
-- TODO: 初期の摂氏と華氏の値を表示する
-- TODO: セッターを使用して温度をnewTempに変更する
-- TODO: 更新された摂氏と華氏の値を表示する
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。