Luaでオブジェクト指向(再考)・・・継承について

前回の方法では、selfにすべてメソッドを持ってしまうので、メモリ食い過ぎますね。

そこで、もうちょっと世の中の人に聞いてみると、Minecraftとタートルと僕さんがさらにいい方法を紹介してくれていました。
つまり、クラスのテーブル本体にメソッド定義を書いて、インスタンスは metatable の __index 経由で参照させるという方法です。これならば、余分なメモリを食いませんね。すばらしい・・・

でも、継承については書かれていませんでしたので、いろいろ考えてみた結果こんなふうに書いていくことになりましたとさ・・・ついでに、私好みに見通しがいいコードを目指しました。

結局superを使うことは出来ず、クラス名で親クラスを指定してメソッドを呼び出す形に落ち着きました。

構造の見通しがよいように、インスタンスを作る関数instance()を定義します。

function instance(class, super, ...)
    local self = (super and super.new(...) or {})
    setmetatable(self, {__index = class})
    setmetatable(class, {__index = super})
    return self
end

この継承の仕組みはインスタンスもクラスを継承していると言えますな。
なので、インスタンスと親クラス、それぞれが継承するように metatabelの__indexに書いてあげる必要があるのです。

この関数の使い方は、自クラス名、親クラス名、引数を順に指定します。
これで、継承されたインスタンスを取得できるというわけです。
また、Luaは引数の省略ができるので、親クラスがない場合は引数を書かないという形で利用します。
ちなみに、... は可変引数のテーブル変数名ですな。これが、擬似コードじゃなくて本物であるところがおもしろい。

で、クラス定義はこんな感じ

-- クラス定義
Human = {
    -- コンストラクタ
    new = function(name, age, sex)
        local self = instance(Human) -- インスタンス作成
        self.name = name
        self.age = age
        self.sex = sex
        return self
    end;

    -- メソッド定義
    disp = function(self)
        print(self.name..(self.sex=="M" and " くん" or " ちゃん")
            .."("..self.age..") - "..self.sex)
    end;
}

-- クラス継承
HumanEx = {
    -- コンストラクタ
    new = function(name, age, sex, addr)
        local self = instance(HumanEx, Human, name, age, sex)  -- インスタンス作成(継承):親クラスHumanと引数を渡す
        self.addr = addr
        return self
    end;

    -- メソッドオーバーライド
    disp = function(self)
        Human.disp(self) -- 親クラスのメソッド呼び出し
        print("ADDR : "..self.addr)
    end;
}

-- さらに継承
HumanDx = {
    -- コンストラクタ
    new = function(name, age, sex, addr, comment)
        local self = instance(HumanDx, HumanEx, name, age, sex, addr)
        self.comment = comment
        return self
    end;
    
    -- メソッドオーバーライド
    disp = function(self, title)
        HumanEx.disp(self)
        print(title.." : "..self.comment)
    end;
}

なんか、テーブル定義のキー名をクォーテーションで括らなくて良い意味がわかったよ。
こんな感じで、使わせたかったんだねきっと・・・うん。
しかも、各要素の区切りが ',' でも ';' でもいいのが変だな??って思ってたけど、テーブル内に直接定義する時には ',' より ';' の方が自然な感じがするかもね☆
なるほど、よく考えられてたのね・・・

で、呼び出し側のコードは前回同じ
あ!うそ!でした。新しい型はfunctionじゃないので、new()メソッドを呼び出します!!
aiue = Human.new("あいうえ", 44, "M")
wawon = HumanDx.new("わをん", 22, "F", "銀河系第三惑星","継承してみた")

aiue:disp()
wawon:disp("コメント")

うん、我ながら、なかなか綺麗に実装できたようですな。
めでたし、めでたし。

ふんどしの持ち主

コメント

このブログの人気の投稿

Pythonのソースファイルの行番号を取得したい

Raspberry Pi 3 シリアルコンソール&シリアル通信

学習リモコン「リモコソ (RIMOKOSO1)」の設定