Menu
Coddy logo textTech

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

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

変数 count はクロージャのスコープ内に存在します。外部のコードからは完全に隠蔽された状態になります。(現状の記述では、まだ何も count をキャプチャしていません。次のレッスンでは、コンストラクタ内に count をクロージャとして取り込むメソッドを追加し、オブジェクトが存在する限り保持されるようにします。)単なる「控えめな提案」に過ぎない self._count とは異なり、このアプローチではデータへのアクセスを真に不可能にします。

もちろん、隠されたデータは、それとやり取りすることができなければ役に立ちません。次のレッスンでは、これらのプライベート変数を読み取り、変更できるメソッドをコンストラクタ内に定義する方法を学びます。これにより、真のカプセル化を維持しながら、制御されたアクセスが可能になります。

challenge icon

チャレンジ

簡単

クロージャを使用して真のデータプライバシーを実証する SecretKeeper クラスを作成しましょう!前のレッスンのアンダースコア記法とは異なり、このアプローチではオブジェクトの外部からデータに一切アクセスできなくなります。

コードは2つのファイルに分けて構成します:

  • SecretKeeper.lua: シークレットを self ではなく、コンストラクタ内のローカル変数に保存するクラスを作成します。コンストラクタ :new(secretValue) は、クロージャ内のみに存在するローカル変数にシークレットを保存する必要があります。現時点では、オブジェクトはこのシークレットにアクセスする手段を持ちませんが(これは次のレッスンで行います!)、オブジェクトが動作することを確認するために、インスタンスに "Keeper" と設定された公開フィールド name も保存してください。
  • main.lua: SecretKeeper モジュールを require し、入力からシークレットの値を読み取ります。そのシークレットを使用して SecretKeeper インスタンスを作成します。次に、以下の3つを出力することで、シークレットが真にプライベートであることを示してください:
    • keeper の name フィールド(Keeper と出力されるはずです)
    • keeper.secret にアクセスしようとしたときの結果(nil になるはずです)
    • keeper._secret にアクセスしようとしたときの結果(これも nil になるはずです)

1つの入力を受け取ります:

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

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

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