プライバシーのためのクロージャ
CoddyのLuaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 44/70。
命名規則は意図を示しますが、実際にアクセスを防ぐわけではありません。誰でも player._health = 999 と記述でき、それは機能します。真のプライバシーのために、Lua では別のアプローチが用意されています。それは、データを self 上ではなく、コンストラクター内の local 変数に保存する方法です。
重要なポイントは、function 内の local 変数には、その function と、その内部で定義された function からのみアクセスできるということです。これは closure と呼ばれます。
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 は closure のスコープ内に存在します。外部のコードからは完全に隠されたままです。(現時点では、まだ何も count をキャプチャしていません。次のレッスンでは、コンストラクター内に count を閉じ込めるメソッドを追加するので、オブジェクトが存在する限り count は保持されます。)単なる丁寧な提案にすぎない self._count とは異なり、この方法ではデータに本当にアクセスできなくなります。
もちろん、決して操作できないのであれば、隠されたデータは役に立ちません。次のレッスンでは、コンストラクター内でメソッドを定義して、これらのprivate変数をreadおよび変更する方法を学びます。これにより、真のカプセル化を維持しながら、制御されたアクセスを提供できます。
チャレンジ
簡単SecretKeeper クラスを作成して、クロージャを使った真のデータプライバシーを示しましょう!前のレッスンで扱ったアンダースコアの慣例とは異なり、この方法ではオブジェクトの外部からデータに本当にアクセスできなくなります。
コードを2つのファイルに分けて整理します。
SecretKeeper.lua:秘密の値をself上ではなく、コンストラクター内のローカル変数に保存するクラスを作成します。コンストラクター:new(secretValue)は、クロージャ内だけで存在するローカル変数に秘密の値を保存する必要があります。現時点では、オブジェクトからこの秘密の値にアクセスする方法はありません(これは次のレッスンで扱います!)。ただし、オブジェクトが正しく動作することを確認できるように、インスタンスに"Keeper"が設定された public なnameフィールドも保存してください。main.lua:SecretKeeper モジュールを Require し、入力から秘密の値を読み取ります。その秘密の値を使って SecretKeeper のインスタンスを作成します。次に、以下の3つを出力して、秘密の値が本当に private であることを示します。- keeper の
nameフィールド(Keeperと出力されるはずです) keeper.secretにアクセスしようとしたときに起こること(nilになるはずです)keeper._secretにアクセスしようとしたときに起こること(これもnilになるはずです)
- keeper の
入力は1つ受け取ります。
- 保存する秘密の値(例:
TopSecret123)
出力は、public フィールドが機能する一方で、秘密の値が完全に隠されていることを示す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>")
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Luaオンラインコンパイラ