Luaでオブジェクト指向表記
Lua速習で述べたとおり、数々のテーブルへのシンタックスシュガーは、オブジェ言うと指向型言語のような表記をするために定義されています。これらを使ってオブジェクト指向のように書いてみます。
クラス表現
安易に考えられるのが、クラスとしてテーブルを用意し、newをメンバに持ち、returnでインスタンスを表すテーブルを返す方法です。インスタンスを表すテーブルのメンバとしては、メンバ変数と関数を持ちます。
関数はobject.method = function(self, 引数)と定義できますが、定義にもシンタックスシュガーがあり、function:method(引数)と書けます。第1引数に自動的にselfが入ります。thisではなくselfです。
Class = { new = function(x) local self = {}; self.member = x; function self:method(x) print("Class::method()"); return x * self.member; end return self; end, Method = function() print("Class::Mehod()"); end }; obj = Class.new(10); print(obj:method(5)); -- 50 Class:Method();
ところで、
obj = Class.new(10); obj2 = Class.new(10); print(obj.method == obj2.method);
はtrueでしょうか? falseでしょうか? 答えはfalseです。newするたびにメンバのmethodを作ってしまいます。オブジェクトがたくさんあるときは効率が悪くなります。そこで、関数は1度だけ定義してメタテーブルに格納する手法を採ります。
Class = { new = function(x) local self = {}; self.member = x; setmetatable(self, { __index = Class }); return self; end, method = function(self, x) print("Class::method()"); return x * self.member; end, Method = function() print("Class::Mehod()"); end }; obj = Class.new(10); obj2 = Class.new(10); print(obj:method(5)); -- 50 print(obj.method == obj2.method); -- true Class:Method();
__indexにClassを入れることにより、メタテーブルのうち、Classであるもの(new, method, Method)を参照できるようにします。これは生成されたオブジェクトから再びnewすることができることを意味しています。
obj = Class.new(10); obj2 = obj.new(100); print(obj:method(5)); print(obj2:method(5));
もちろんできないようにすることもできますが、冗長な記述となるので今回はこのように書きました。
プライベートなメンバ・メソッド
ブロックの中で宣言することでローカル変数が使えます。これを使ってプライベートなメソッドを定義します。但し、プライベートなメンバはクラス変数に相当します。全てのオブジェクトで共有されてしまいます。
local Class = {}; do local p_method; local p_Member; Class.new = function(x) local self = {}; self.member = x; p_Member = x; setmetatable(self, { __index = Class }); return self; end Class.method = function(self, x) print("Class::method()"); p_method(self, x); print("Class::p_Member", p_Member); return x * self.member; end function p_method(self, x) print("Class::p_method()", x); end end obj = Class.new(10); print(obj:method(5));
プライベートなメンバは、オブジェクトと対応するテーブルを1つ定義して、その中に入れます。何らかのシンタックスシュガーがあれば便利なのですが。プライベートメソッドも同様に格納してみました。
local Class = {}; do local _ = {}; function Class:new(x) local this = {}; _[this] = {}; this.member = x; _[this].p_member = x; setmetatable(this, { __index = self }); setmetatable(_[this], { __index = _ }); return this; end function Class:method(x) print("Class::method()"); _[self]:p_method(x); print("Class::p_member", _[self].p_member); return x * self.member; end function _:p_method(x) print("Class::p_method()", x); end end obj = Class.new(10); obj2 = Class.new(20); print(obj:method(5)); -- 50 print(obj2:method(3)); -- 60
継承
継承は基底クラスをコンストラクタで生成して、その結果を返します。但し、__indexには自身のクラスを指定して上書きします。そうすると基底クラスのメソッドが実行できません。そこで、自身のクラスの(インスタンスではなく元のClassです)__indexに基底クラスを入れます。こうすることで基底クラスのメソッドが探索されます。
local Base = {}; do local _ = {}; function Base:new() local this = {}; _[this] = {}; setmetatable(this, { __index = self }); setmetatable(_[this], { __index = _ }); return this; end function Base:funcA() print("Base::funcA()"); end end local Class = {}; setmetatable(Class, { __index = Base }); -- 注目 do local _ = {}; function Class:new() local this = Base:new(); -- 注目 _[this] = {}; setmetatable(this, { __index = self }); setmetatable(_[this], { __index = _ }); return this; end function Class:funcB() print("Class::funcB()"); end end obj = Class:new(); obj:funcA(); obj:funcB();