Luaリファレンス 要注意点 ~オブジェクト指向~

インボカント ~暗黙の第1引数self~

  • PerlやPythonでは、明記必須のインボカント。Luaにも存在します。
    インボカントこそ、手続き型言語をオブジェクト指向化する中核概念といえるでしょう。
    Account = { balance = 0 }
    function Account.withdraw (v)
        Account,balance = Account.balance - v
    end
    
    これはテーブル内にメソッド的なものを定義した例ですが、これだと
    a = Account
    Account = nil
    a.withdraw(10)  ---> エラー
    
    となってしまいます。
  • そこで、第1引数とインボカントが活躍します。
    function Account.withdraw ( self, v )
        self.balance = self.balance - v
    end
    
  • 多くの言語では、1番目のself(またはthisとなっている言語も多い)の引数は見えなくなっています。
    function Account:withdraw( v )    ---> 「.」ではなく、「:」を使うと、1番目の引数にselfを書いたことと同じこととみなされる。
        self.balance = self.balance - v
    end
    
  • 呼び出しも「:」を使えば第1引数にオブジェクト自体を代入したことと同じとなる。
    通常記法も使えるがその場合はやや間が抜けた記述となるだろう。
    a = Account
    Account = nil
    a.withdraw(a, 10)  --> 通常記法。第1引数にテーブル(オブジェクト)自身を渡す。
    a:withdraw(10)     -->  シンタックスシュガー。第1引数にオブジェクト自身を渡していることになる。
    

インボカントとインスタンス化

  • インボカント
    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
    
    a = Account:new{ balance = 0 }
    a:deposit(100)
    
    としたとすると、
    Account:new { balance = 0 }
    
    などといったように使用すると、メタテーブルの__indexに対して
    Account.__index = Account
    
    となる。

    そして、a:deposit(100)は a.deposit(a, 100)となるので、
    aに存在しないdepositメソッドは
    getmetatable(a).__index.deposit(a, 100) と検索される。

    結果、Account.deposit(a, 100) と指定したことと同じことになる。

    即ち、aは自身にメソッドが存在しない時、Account(を一種親的にみなし)呼び出す、 一種、オブジェクト指向の「継承」や「デフォルト値」があるかのような機構を構築出来るということである。
  • フィールドの初期値の継承 又、フィールドの初期値も継承する。
    例えば、
    b = Account:new()
    print(b.balance) -- b.balance は b自体には存在しないので、Account.balance を見ることとなる。
    
    一方、これにたいして、
    b:deposit(10)
    
    などと呼び出した場合は、Account.deposit(b, 10)が呼び出される形となり、b.blanace = b.balance + 10 されるので、
    この段階で、b自身にbalanceフィールドが形成されることとなる。
  • クラスの継承
    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()  -- この段階ではまだインスタンス
    s = SpecialAccount:new{limit = 1000.00}  -- new を実行した時、selfのパラメータがSpecialAccountとなり、self.__indexもSpecialAccountとなった。
    
    これにより、sのメタテーブルはSpecialAccountとなり、sはSpecialAccountのインスタンスであるかのように振舞える。

    s:deposit(100)
    
    を呼び出すと、
    getmetatable(s).__index.deposit(s, 100)となるので、
    ScecialAccount.deposit(s, 100)を実行しようとするが、このdepositも存在しないため、
    getmetatable(SpecialAccount).__index.deposit(s, 100)となり、
    結果、
    Account.deposit(s, 100)が実行される。

    即ち、インスタンス、子クラス、親クラスの、メソッド探査(階層チェーンの伝播)が構築できたこととなるのである。
  • 又、SpecialAccountは独自のメソッドを持ったり、上書き(オーバライド)するのになんら問題はない。
    function SpecialAccount:withdraw (v)   --オーバライド
        if v - self.blance >= self:getLimit() then
            error("引き出しすぎ")
        end
        self.balance = self.balance - v
    end
    
    function SpecialAccount:getLimit()
        return self.limit or 0
    end
    
    又、プロトタイプ言語であることからわかるように、いちいちクラスメソッドの単位でオーバーライドせず、インスタンス単位で変えても良い(もともと区分けがないとも言えるが…)
    function s:getLimit()
        return self.balance * 0.10
    end
    
    これで、s:withdrawから呼び出されるgetLimitは、このs:getLimitが呼ばれることとなる。

多重継承

  • Account クラスと Named クラスを多重継承する。
    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
    
    ----
    
    Name = {}
    function Named:getname()
        return self.name
    end
    
    function Named:setname(n)
        self.name = n
    end
    
    ---
    
    local function search (k ,plist)
        for i=1, #plist do
            local v = plist[i][k]
            if v then return v end
        end
    end
    
    function MultiInheritance (...)  -- 複数のクラスから
        local c = {}                        -- 新しいクラス
        local parents = {...}
    
        setmetatable(c, {__index = function(t, k)
            local v = search(k, parents)   -- 該当プロパティを持つ親クラスを特定する
            t[k] = v  -- パフォーマンス改善のためコピーしておく
                return v
        end})
    
        c.__index = c
    
        -- 新しいクラスのnewコンストラクタを定義
        function c:new (o)
            o = o or { }
            setmetatable(o, c)
            return o
        end
    
        return c
    end
    
    NamedAccount = MultiInheritance(Account, Named)
    
    account = NamedAccount:new{name="Paul"}
    

プライベート

  • Perlと同じやり方。

    ただ、わかりにくくなるので、LUAではあまりやらない。
    アンダーバーでも付けて、人と運用のレベルでカバーするのがLUAでは普通で
    強制的に文法で対応しようとはしない方が良い。
    function newAccount ( initialBalance)
        local self = {balance = initialbalance}
        
        local deposit = function (v )
                            self.balance = self.balance -v
                        end
    
        local getBalance = function () return self.balance end
     
        return {
            deposit = deposit
            getBalance = getBalance
        }
    end
    
    acc = newAccount(100)
    print(acc.getBalance())
    

オブジェクトの簡易実装はクロージャで

まぁ、他の言語でも全く同じ使用用途。 しかし、複雑化すると、結局クラス化することが多い

function newObject(value)
    return function(action, v)
        if action == "get" then return value
        elseif action == "set" then value = v
        else error "invalid action"
        end
    end
end

d = newObject(0)
print(d("get")) --> 0

d("set", 10)
print(d("get")) --> 10

トップ   差分 履歴 リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-02-04 (木) 00:00:00