Menu
Coddy logo textTech

プライバシーのためのクロージャ

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.countnil を返し、回避するためのアンダースコアで始まるフィールドもありません。

変数 countclosure のスコープ内に存在します。外部のコードからは完全に隠されたままです。(現時点では、まだ何も count をキャプチャしていません。次のレッスンでは、コンストラクター内に count を閉じ込めるメソッドを追加するので、オブジェクトが存在する限り count は保持されます。)単なる丁寧な提案にすぎない self._count とは異なり、この方法ではデータに本当にアクセスできなくなります。

もちろん、決して操作できないのであれば、隠されたデータは役に立ちません。次のレッスンでは、コンストラクター内でメソッドを定義して、これらのprivate変数をreadおよび変更する方法を学びます。これにより、真のカプセル化を維持しながら、制御されたアクセスを提供できます。

challenge icon

チャレンジ

簡単

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 になるはずです)

入力は1つ受け取ります。

  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>")
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Luaオンラインコンパイラ