Luaでオブジェクト指向プログラミングをする方法についてまとめます。
はじめに
本記事ではLuaでオブジェクト指向プログラミングを行う方法についてまとめます。
Luaについての基礎知識については以下の記事にまとめていますので、必要に応じて参照してください。
メタテーブル
さてLuaでオブジェクト指向プログラミングをするためには前提知識としてメタテーブルを理解する必要があります。
Luaでは全ての値がメタテーブルを持ちますが、特にテーブルに対してメタテーブルを設定すると、テーブルの動作を上書きできます。
この仕組みを理解するために以下のような3次元の値を表現するVector3テーブルを定義します。
local Vector3 = {} Vector3.new = function(x, y, z) local obj = {} obj.x = x obj.y = y obj.z = z obj.log = function(self) print(string.format("{%d, %d, %d}", self.x, self.y, self.z)) end return obj end
次にこの三次元の値を加算することを考えます。
まずは以下のように普通に+演算しで加算してみます。
local pos1 = Vector3.new(1, 2, 3) pos1:log() -- {1, 2, 3} local pos2 = Vector3.new(4, 5, 6) pos2:log() -- {4, 5, 6} -- 加算できないのでこれはエラーになる local pos3 = pos1 + pos2 pos3:log()
すると、attempt to perform arithmetic on a table value
というメッセージとともにエラーが出力されます。
テーブル同士は加算できないためエラーになっています。
ここで、テーブル同士を加算できるようにするためにメタテーブルを使います。
local Vector3 = {} Vector3.new = function(x, y, z) local obj = {} obj.x = x obj.y = y obj.z = z obj.log = function(self) print(string.format("{%d, %d, %d}", self.x, self.y, self.z)) end return obj end -- Vector3同士を加算する関数を定義 local AddVector3 = function(a, b) local x = a.x + b.x local y = a.y + b.y local z = a.z + b.z return Vector3.new(x, y, z) end local pos1 = Vector3.new(1, 2, 3) local pos2 = Vector3.new(4, 5, 6) -- pos1の加算演算子をAddVector3で上書きする setmetatable(pos1, {__add = AddVector3}) local pos3 = pos1 + pos2 pos3:log() -- {5, 7, 9}
このようにメタテーブルの__addキーに加算用の関数を代入しておくと、そのテーブルの加算処理の挙動を上書きすることができます。
以上がメタテーブルの基本的な使い方です。
メタテーブルの__indexキーについて
さて前節ではメタテーブルに__addキーを設定することで加算処理を上書きすることができました。
メタテーブルに使えるキーには他にも様々なものが存在します。
そのうちの一つとして、__indexキーがあります。
これはテーブルへのインデックスアクセスを上書きするものです。
例として、以下のように2次元の値を表すテーブルを定義して、
テーブルに存在しない変数zを出力しようとしてみます。
local Vector2 = {} Vector2.new = function(x, y) local obj = {} obj.x = x obj.y = y return obj end local pos1 = Vector2.new(1, 2) print(pos1.z) -- nil
結果はテーブルにzが存在しないためnilになります。
ここで、以下のようにインデックスアクセスを変数zを持つVector3テーブルで上書きしてみます。
local Vector2 = {} Vector2.new = function(x, y) local obj = {} obj.x = x obj.y = y return obj end local Vector3 = {} Vector3.new = function(x, y, z) local obj = {} obj.x = x obj.y = y obj.z = z return obj end local pos1 = Vector2.new(1, 2) local pos2 = Vector3.new(3, 4, 5) -- pos1のインデックスアクセスをpos2で上書きする setmetatable(pos1, {__index = pos2}) print(pos1.z) -- 5
インデックスアクセスが上書きされてnilではなく5と出力されることが確認できました。
ちなみにもしVector2に変数zが定義されていた場合には、
インデックスアクセスを上書きしてもVector2に定義したものが優先されます。
local Vector2 = {} Vector2.new = function(x, y) local obj = {} obj.x = x obj.y = y obj.z = 100 -- こっちが優先される return obj end
オブジェクト指向プログラミング
それでは以上の知識を用いてLuaでオブジェクト指向プログラミングを行います。
実装方法はいくつか考えられますが、今回は以下のサイトの方法を紹介させていただきます。
まず以下のようにInstance関数を定義します。
この中で前節の__indexキーが使用されていることがわかります。
function Instance(class, super, ...) local self = (super and super.new(...) or {}) setmetatable(self, {__index = class}) setmetatable(class, {__index = super}) return self end
クラスはこの関数を使って以下のように定義します。
Foo = { new = function(name) local self = Instance(Foo) -- インスタンス作成 self.name = name return self end; -- メソッド log = function(self) print('foo') end; }
このクラスは以下のように使います。
local foo = Foo.new('fooExample') foo:log() -- foo: fooExample
また、継承したクラスは以下のように定義します。
Bar = { new = function(name, message) local self = Instance(Bar, Foo, name) self.message = message return self end; -- メソッドをオーバーライド log = function(self) -- Foo.log(self) -- 親クラスの関数を呼ぶのはこんな感じに print('bar: '..self.name..' / '..self.message) end; }
これは以下のようにして使います。
local bar = Bar.new('barExample', 'messageExample') bar:log() -- bar: barExample / messageExample