読者です 読者をやめる 読者になる 読者になる

プログラミングの魔物

エラー、バグ、仕様変更と戦うブログ

Luaのオブジェクト指向プログラミング考察2

昨日の記事ではLuaのモジュールでクラス定義とプライベート変数を扱う方法について考察した。
今回はモジュールで継承とプライベート関数を表現する方法を考える。

ディレクトリ構成

libs/mod.lua
libs/sub.lua
main.lua

親クラス(mod.lua

local _M = {}
local _ = {}	--プライベート変数を保持するテーブル
setmetatable(_, {__mode = "k"})  --プライベート変数を弱いテーブルにする。考察3を参照

--コンストラクタnewは受け取ったテーブルの要素からプライベート変数を初期化する
function _M:new(o)
  o = o or {}
  _[o] = {a = o._a}
  o._a = nil
  self.__index = self
  return setmetatable(o, self)
end

--プライベート関数xは与えられた数を二乗する
local function x(n)
  return n * n
end

--パブリックな関数getValue
function _M:getValue(v)
  return x(_[self].a)
end

return _M

サブクラス(sub.lua

local mod = require "libs.mod"
local _M = mod:new()

--コンストラクタを再定義する
function _M:new(o)
  o = mod.new(self, o)
  --サブクラス用の初期化処理
  return o
end

--親クラスの関数getValue()を使う
function _M:show()
  print(self:getValue())
  --print(_[self].a)    --親クラスのプライベート変数は参照できないのでエラー
end

return _M

プログラム本体(main.lua

mod = require "libs.mod"
sub = require "libs.sub"

o1 = mod:new{_a = 3}
o2 = sub:new{_a = 5}
o3 = sub:new{_a = 10}

print(o1:getValue())  --> 9
o2:show()  --> 25
o3:show()  --> 100

この例の場合はサブクラスでコンストラクタを書く必要がないので省略することも可能。
ただし、サブクラスでもプライベート変数を用意する場合はコンストラクタの再定義は必要になる。

サブクラス内のrequireでパッケージを指定しているのでディレクトリ構成は必須。
ちょっと凝ったクラスを作る場合はモジュールでクラスを作るのが良さそう。

継承はメインでrequireしているので、代わりにlibs.mod:new(_M)でも継承できる。
あるいは直接メタテーブルを設定してもいい。