0Pricing
Lua Academy · レッスン

クロージャによるカプセル化

クロージャを利用したオブジェクトパターンで、非公開状態を隠蔽します。

「クロージャによるカプセル化」はCoddyKit上の無料Lua Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLua Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Lua Academyコースには全4レッスンが含まれています。

クロージャーベースのオブジェクト

メタテーブルを使うOOPに代わる方法として、クロージャーで状態を隠蔽できます。オブジェクトは関数のテーブルであり、すべての関数がプライベート変数をクロージャーとして保持します。メタテーブルは必要ありません。外部から到達できない、真にプライベートな状態を実現できます。

local function makeCounter(start)
  local count = start or 0   -- truly private
  return {
    increment = function() count=count+1 end,
    decrement = function() count=count-1 end,
    get       = function() return count end,
    reset     = function() count=0 end,
  }
end

local c = makeCounter(10)
c.increment(); c.increment()
print(c.get())   -- 12
print(c.count)   -- nil (truly private!)

プライベートとパブリック

クロージャーでは、プライベートとパブリックの区別が明確です。クロージャーはupvalue(プライベート)を共有し、外部コードからは返されたテーブル(パブリック)だけが見えます。パブリックAPIを経由せずにプライベート変数へアクセスする方法はありません。

local function makeAccount(initial)
  local balance = initial
  local transactions = {}
  
  local function record(type, amount)
    transactions[#transactions+1] = {type=type,amount=amount}
  end
  
  return {
    deposit  = function(n) record("D",n); balance=balance+n end,
    withdraw = function(n)
      if n>balance then error("insufficient funds") end
      record("W",n); balance=balance-n
    end,
    balance  = function() return balance end,
    history  = function() return transactions end,
  }
end

共有クロージャー

同じ返却テーブル内の複数の関数は、同じupvalueを共有します。これにより、状態を外部に公開せずにメソッド間で共有できます。すべてのメソッドが同じプライベート変数を参照します。

local function makeQueue()
  local items = {}  -- shared by all methods
  local size = 0
  return {
    enqueue = function(v) size=size+1; items[size]=v end,
    dequeue = function()
      if size==0 then return nil end
      local head=1
      while items[head]==nil do head=head+1 end
      local v=items[head]; items[head]=nil; return v
    end,
    isEmpty = function() return size==0 end,
    count   = function() return size end,
  }
end

クロージャーとメタテーブル

クロージャー:状態が完全にプライベートで、メタテーブルのオーバーヘッドがなく、各インスタンスが関数の独自コピーを持つためメモリを多く使います。メタテーブル:メソッドを共有するためメモリ使用量が少なく、デバッグツールで調査でき、継承をサポートします。プライバシーの要件とインスタンス数に応じて選択してください。

-- Closure: per-instance functions (more memory)
local function makePoint(x,y)
  return {
    x = function() return x end,
    y = function() return y end,
    distance = function()
      return math.sqrt(x*x+y*y)
    end
  }
end

-- Metatable: shared methods (less memory)
-- local Point = {}; Point.__index = Point
-- function Point.new(x,y) return setmetatable({x=x,y=y},Point) end

可変クロージャー

可変変数をクロージャーで保持すると、テーブルをまったく使わずに状態を持つオブジェクトを作成できます。1つのクロージャーで状態を保持し、結果を計算できます。

local function makeAccumulator()
  local sum, count = 0, 0
  return {
    add = function(n) sum=sum+n; count=count+1 end,
    mean = function()
      return count>0 and sum/count or 0
    end,
    total = function() return sum end,
    n     = function() return count end,
  }
end

local acc = makeAccumulator()
for _, v in ipairs({10,20,30,40,50}) do
  acc.add(v)
end
print(acc.total(),acc.mean(),acc.n())  -- 150  30.0  5

検証付きコンストラクター

クロージャーベースのコンストラクターでは、メソッド内ではなくファクトリ関数で入力を検証します。入力が無効な場合、オブジェクトが作成される前にファクトリがnilまたはエラーを返します。

local function makeRange(lo, hi)
  assert(lo<=hi,"lo must be <= hi")
  return {
    contains = function(v) return v>=lo and v<=hi end,
    clamp    = function(v) return math.max(lo,math.min(hi,v)) end,
    lo       = function() return lo end,
    hi       = function() return hi end,
    span     = function() return hi-lo end,
  }
end

local r = makeRange(0,100)
print(r.contains(50))   -- true
print(r.clamp(150))     -- 100

クロージャーによるイベント

クロージャーで実装したイベントエミッターでは、ハンドラーのリストがプライベートになり、add/remove/emitがパブリックAPIになります。

local function makeEmitter()
  local handlers = {}
  return {
    on = function(event, fn)
      handlers[event] = handlers[event] or {}
      handlers[event][#handlers[event]+1] = fn
    end,
    emit = function(event, ...)
      for _, fn in ipairs(handlers[event] or {}) do
        fn(...)
      end
    end,
    off = function(event) handlers[event] = {} end,
  }
end

local emitter = makeEmitter()
emitter.on("data", function(v) print("Got:", v) end)
emitter.emit("data", 42)   -- Got: 42

クロージャーによるメモ化

クロージャーを使ったメモ化ラッパーでは、キャッシュとラップされた関数がプライベートになり、メモ化されたバージョンだけが返されます。

local function memoize(fn)
  local cache = {}
  return function(...)
    local key = table.concat({...}, ",")
    if cache[key] == nil then
      cache[key] = fn(...)
    end
    return cache[key]
  end
end

local expensiveFib
expensiveFib = memoize(function(n)
  if n<=1 then return n end
  return expensiveFib(n-1)+expensiveFib(n-2)
end)

for i=0,10 do io.write(expensiveFib(i).." ") end
print()

クロージャーオブジェクトの拡張

元のオブジェクトのメソッドを呼び出す新しいオブジェクトを作成することで、クロージャーベースのオブジェクトを拡張できます。これは継承ではなくラッピングに近く、元のオブジェクトに処理を委譲します。

local function makeTimedCounter(start)
  local base = makeCounter and makeCounter(start) or (function()
    local c=start or 0
    return {inc=function()c=c+1 end,get=function()return c end}
  end)()
  local callCount = 0
  return {
    increment = function()
      callCount=callCount+1
      base.inc()
    end,
    get = base.get,
    calls = function() return callCount end,
  }
end

クロージャーを使う場面

真にプライベートで安全に保護する必要がある状態を扱う場合、インスタンス数が少なくメモリが問題にならない場合、または関数型スタイルのコードを書く場合は、クロージャーベースのOOPを使用します。継承が必要な場合、インスタンス数が多い場合、またはOOPライブラリと統合したい場合は、メタテーブルを使用します。

-- Guideline summary:
-- Closures: true privacy, no metatable, self-contained
-- Metatables: shared methods, inheritance, instanceof checks

-- Rule of thumb:
-- < 100 instances with private state -> closures
-- > 100 instances OR need inheritance -> metatables

print("Both patterns are valid Lua OOP.")

クイックチェック

メタテーブルベースのオブジェクトに対する、クロージャーベースのオブジェクトの主な利点は何ですか?

復習:カプセル化のためのクロージャー

まとめ:

  • クロージャーパターン:ファクトリ関数がメソッドテーブルを返し、状態はupvalueになる
  • 完全にプライベートで、外部からupvalueへアクセスする方法がない
  • 返されたすべてのメソッドが同じプライベートupvalueを共有する
  • トレードオフ:インスタンスごとのメモリ使用量は増えるが、プライバシーは向上する
  • セキュリティが重要なコードや関数型スタイルの設計に使用する

よくある質問

「クロージャによるカプセル化」レッスンは無料ですか?

はい。「クロージャによるカプセル化」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Lua Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Lua Academyコースには全4レッスンが含まれています。

「クロージャによるカプセル化」で何を学びますか?

クロージャを利用したオブジェクトパターンで、非公開状態を隠蔽します。 ブラウザで直接実行するハンズオンコードでLua Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Lua Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのLua Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「クロージャによるカプセル化」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このLua Academyレッスンでコードを書いて実行できますか?

はい。すべてのLua Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. メタテーブルによるクラス
  2. コンストラクターとnew()
  3. インスタンスメソッドとself
  4. クロージャによるカプセル化
← Lua Academyに戻る