プライバシーのためのクロージャ
CoddyのLuaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 44/70。
命名規則は意図を示しますが、実際にアクセスを防ぐわけではありません。誰でも player._health = 999 と書くことができ、それは動作してしまいます。真のプライバシーを実現するために、Luaは別の手法を提供しています。それは、self ではなくコンストラクタ内のローカル変数にデータを保存する方法です。
重要なポイントは、関数内のローカル変数はその関数内、およびその内部で定義された関数からのみアクセス可能であるということです。これはクロージャと呼ばれます:
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- 本当にプライベートです!selfには含まれません。
local obj = {}
setmetatable(obj, Counter)
return obj
endこの例では、count は :new() 関数の内部にのみ存在します。obj には格納されていないため、外部からアクセスする方法はありません。myCounter.count は nil を返し、回避するためのアンダースコア付きのフィールドも存在しません。
変数 count はクロージャのスコープ内に存在します。外部のコードからは完全に隠蔽された状態になります。(現状の記述では、まだ何も count をキャプチャしていません。次のレッスンでは、コンストラクタ内に count をクロージャとして取り込むメソッドを追加し、オブジェクトが存在する限り保持されるようにします。)単なる「控えめな提案」に過ぎない self._count とは異なり、このアプローチではデータへのアクセスを真に不可能にします。
もちろん、隠されたデータは、それとやり取りすることができなければ役に立ちません。次のレッスンでは、これらのプライベート変数を読み取り、変更できるメソッドをコンストラクタ内に定義する方法を学びます。これにより、真のカプセル化を維持しながら、制御されたアクセスが可能になります。
チャレンジ
簡単クロージャを使用して真のデータプライバシーを実証する SecretKeeper クラスを作成しましょう!前のレッスンのアンダースコア記法とは異なり、このアプローチではオブジェクトの外部からデータに一切アクセスできなくなります。
コードは2つのファイルに分けて構成します:
SecretKeeper.lua: シークレットをselfではなく、コンストラクタ内のローカル変数に保存するクラスを作成します。コンストラクタ:new(secretValue)は、クロージャ内のみに存在するローカル変数にシークレットを保存する必要があります。現時点では、オブジェクトはこのシークレットにアクセスする手段を持ちませんが(これは次のレッスンで行います!)、オブジェクトが動作することを確認するために、インスタンスに"Keeper"と設定された公開フィールドnameも保存してください。main.lua: SecretKeeper モジュールを require し、入力からシークレットの値を読み取ります。そのシークレットを使用して SecretKeeper インスタンスを作成します。次に、以下の3つを出力することで、シークレットが真にプライベートであることを示してください:- keeper の
nameフィールド(Keeperと出力されるはずです) keeper.secretにアクセスしようとしたときの結果(nilになるはずです)keeper._secretにアクセスしようとしたときの結果(これもnilになるはずです)
- keeper の
1つの入力を受け取ります:
- 保存するシークレットの値(例:
TopSecret123)
出力は、公開フィールドは機能する一方で、シークレットは完全に隠されていることを示す3行にする必要があります:
Name: Keeper
secret: nil
_secret: nilどのようなシークレットを渡しても、出力は同じになります。なぜなら、アクセスする方法がないからです!コンストラクタ内のローカル変数は外部からは見えません。これは、クロージャベースのプライバシーがアンダースコアの命名規則とは根本的に異なることを証明しています。
例えば、入力が MyPassword の場合でも、出力は以下のようになります:
Name: Keeper
secret: nil
_secret: nil自分で試してみよう
-- SecretKeeper モジュールを読み込む
local SecretKeeper = require('SecretKeeper')
-- 入力から秘密の値を読み取る
local secretValue = io.read()
-- TODO: 秘密の値を使用して SecretKeeper インスタンスを作成する
-- TODO: keeper の name フィールドを出力する (形式: "Name: <name>")
-- TODO: keeper.secret にアクセスした際の結果を出力する (形式: "secret: <value>")
-- TODO: keeper._secret にアクセスした際の結果を出力する (形式: "_secret: <value>")
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。