Menu
Coddy logo textTech

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

CoddyのLuaジャーニー「Object Oriented Programming」セクションの一部 — レッスン 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 はクロージャのスコープ内に存在します。オブジェクトが存在する限り保持されますが、外部のコードからは完全に隠蔽されています。単なる慣習的な提案に過ぎない 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

チートシート

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を返します。self._count(これは単なる命名規則に過ぎません)とは異なり、外部からアクセスする方法はありません。このアプローチにより、データは真にアクセス不可能になります。

この変数はオブジェクトが存在する限り存続しますが、外部コードからは完全に隠されたままになり、真のカプセル化を実現します。

自分で試してみよう

-- 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腕試し

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

Object Oriented Programmingのすべてのレッスン