Lua Object-Oriented Programming(OOP) オブジェクト指向
注意:勉強がてらの覚え書き、間違っている可能性もあります。
鵜呑み注意。
テーブルを利用したオブジェクト指向プログラムライク
Account = {balance = 0} function Account.withDraw(v) Account.balance = Account.balance - v end
この定義は Accountオブジェクト内に新しい関数とフィールドを作成する.次のように呼び出せる。
Account.withDraw(100.00)
しかしこの方法だと次のケースで問題が起きる
a = Account; Account = nil a.withDraw(100.00) -- ERROR
withDraw関数内でAccountが参照されているが、この関数を呼ぶ前にAccountにnilを渡している。
もう少しスマートに
Account = {balance = 0} function Account.withDraw(self, v) self.balance = self.balance - v end
呼び出し↓
a1 = Account; Account = nil a1.withDraw(a1, 100.00) --OK
withDraw関数呼び出しの前にAccountにnilが渡されても問題ない.
ついでにselfパラメータを使うことで,別オブジェクトでも同様にwithDraw関数を使える.↓
a2 = {balance = 0, withDraw = Account.withDraw} a2.withDraw(a2, 290.00)
メタテーブルを利用したオブジェクト指向プログラムライク
メタテーブルとはテーブルと同じ。
ただしテーブル内の値は特定の演算をしたときのみ使われる。
テーブル内の値は決まっている。
- __add : +演算
- __sub : -演算
- __mul : *演算
- __div : /演算
- __mod : %演算
- __pow : ^(累乗)演算
- __unm : 単項演算
- __concat : 連結演算
- __len : #演算
- __eq : ==演算
- __lt : <演算
- __le : <=演算
- __index : table[key]インデックスアクセス
- __newindex : table[key] = valueインデックス代入
- __call : 値を関数呼び出したとき
所謂C++でいうところのoperator*()などになる。
setmetatable(a, {__index = b})
aオブジェクトが持っていないときは,bへオペレーションを探す。
function Account:new(o) o = o or {} --もしUserが提供しない場合は生成される. setmetatable(o, self) self.__index = self return o end
oオブジェクトのメタテーブルをself(=Account)に決定する。
selfにindexアクセスをした場合はself(=Account)を探すように決定する.
oオブジェクトが持っていないときは, self(=Account)へオペレーションを探す。
(indexに設定している理由は、関数呼び出しはテーブルのindexアクセスによるもののため. Account.drawWithはAccount["drawWith"]と同じ)
a = Account:new()
a:deposit(100.00)
↑新しいAccountインスタンスが生成される。
a:deposit(100.00)が呼ばれたとき、テーブルa内にdepositが見つからない場合はメタテーブルの__indexを調査する。
展開するとこんな感じになる。
getmetatable(a).__index.deposit(a, 100.00)
↑aのメタテーブルのキーとなる__indexの中にあるオブジェクトAccountのdeposit関数をコールしている。
つまりは
Account.deposit(a,100.00)
になる。これは初めの方で説明したもの。
aオブジェクトに対してAccountのdeposit関数を使っているのと同じ。
これでAccountのインスタンスが作成でき、
aのインスタンスメソッドが呼ぶことができた。
継承
とりあえず次のような基底クラスみたいなAccountクラスがあったとする。
Account = {balance = 0} function Account:new(o) o = o or {} setmetatable(o, self) self.__index = self return o end function Account:deposit(v) self.balance = self.balance + v end function Account:withDraw(v) if v > self.balance then error "金がねぇ" end self.balance = self.balance - v end
AccountのサブクラスSpecialAccountを定義する.
SpecialAccount = Account:new()
これでSpecialAccountはAccountのインスタンスになる。
そして次で変数追加.
s = SpecialAccount:new{limite = 1000.00}
selfはSpecialAccountを参照しています。
そしてselfはmetatableの__indexに登録されています。
つまりこれで s はSpecialAccountのインスタンスになります。
しかもAccountを継承してます。
↓この式を評価すると
s:deposit(100.00)
sの中にdepositない。SpecialAccountを見に行く。
SpecialAccountの中にdepositない。Accountを見に行く。
Accountの中にdepositある。Account.depositを実行。
こんな感じになります。
オーバーライドを実現
上の評価の流れでいくと、SpecialAccountにwithDrawを定義したらAccountのwithDrawを呼ぶ前にSpecialAccountのwithDrawを呼ぶことになります。
function SpecialAccount:withDraw(v) if v - self.balance >= self.getLimite() then error "金欠" end self.balance = self.balance - v end function SpecialAccount:getLimite() return self.limit or 0 end
参考:http://www.lua.org/pil/index.html:Programming in Lua (first edition)
参考:http://milkpot.sakura.ne.jp/lua/lua51_manual_ja.html:Lua 5.1 リファレンスマニュアル(エンコードによる文字化け注意)