共有と固有
CoddyのLuaジャーニー「Object Oriented Programming」セクションの一部 — レッスン 35/70。
継承を扱う際、各データがどこに存在するかを理解することが重要です。一部の属性は共有されます。つまり、親で定義され、すべての子からアクセス可能です。他の属性は固有です。つまり、子クラスまたはインスタンスで特異的に作成されます。
以下の階層を考えてみましょう:
local Animal = {}
Animal.__index = Animal
Animal.kingdom = "Animalia" -- 共有:クラスで定義
function Animal:new(name)
local obj = {name = name} -- 固有:各インスタンスに保存
setmetatable(obj, Animal)
return obj
endlocal Dog = {}
Dog.__index = Dog
setmetatable(Dog, {__index = Animal})
Dog.sound = "bark" -- すべてのDogで共有されます
function Dog:new(name, breed)
local obj = Animal.new(Animal, name)
obj.breed = breed -- 固有:各Dogは独自の品種を持ちます
setmetatable(obj, Dog)
return obj
endDogのインスタンスを作成すると、一部の値はオブジェクト上に直接存在し、他の値はルックアップチェーンを通じて見つかります:
local rex = Dog:new("Rex", "German Shepherd")
-- このインスタンスに固有(rexに格納)
print(rex.name) -- "Rex"
print(rex.breed) -- "German Shepherd"-- 共有 (ルックアップにより発見)
print(rex.sound) -- "bark" (Dogから)
print(rex.kingdom) -- "Animalia" (Animalから)主な違いは次のとおりです。コンストラクタ内で obj に割り当てられたフィールドは各インスタンスに対して一意ですが、クラス・テーブルに直接定義されたフィールドはそのクラスおよびその子クラスのすべてのインスタンス間で共有されます。
チャレンジ
簡単CreatureクラスとDragonクラスのシステムを構築することで、継承階層を通じてデータがどのように流れるかを探ってみましょう。すべてのインスタンスで共有される属性と、個々のオブジェクトに固有の属性を直接確認できます。
コードは以下の3つのファイルに分けて構成します:
Creature.lua: すべてのクリーチャーが共有するクラスレベルの共有属性world = "Fantasy Realm"を持つ親クラスを定義します。クリーチャーの名前をインスタンス属性として保存する:new(name)コンストラクタを含めてください。Dragon.lua: Creatureを継承する子クラスを定義します。すべてのドラゴンが共有するクラスレベルの共有属性element = "fire"を追加します。:new(name, color)コンストラクタでは、名前のために親コンストラクタを呼び出し、その後、各ドラゴンの固有のインスタンス属性としてcolorを追加する必要があります。main.lua: 2つの異なるドラゴンを作成し、それらの属性を調べて、共有データと固有データの違いを示します。
2つの入力を受け取ります:
- 1つ目のドラゴンの名前と色。カンマで区切られています(例:
Smaug,red) - 2つ目のドラゴンの名前と色。カンマで区切られています(例:
Draco,gold)
メインファイルで、それぞれの名前と色を持つ両方のドラゴンを作成します。次に、以下の情報をこの正確な順序で出力してください:
- 1つ目のドラゴンの
name - 1つ目のドラゴンの
color - 2つ目のドラゴンの
name - 2つ目のドラゴンの
color - 1つ目のドラゴンの
element(Dragonクラスから共有) - 2つ目のドラゴンの
element(Dragonクラスから共有) - 1つ目のドラゴンの
world(Creatureクラスから共有)
例えば、入力が Smaug,red と Draco,gold の場合、出力は以下のようになります:
Smaug
red
Draco
gold
fire
fire
Fantasy Realmname と color が各ドラゴンで異なっている(固有のインスタンス属性)一方で、element と world は両方で同じである(ルックアップチェーンを通じて見つかる共有クラス属性)ことに注目してください。
チートシート
継承階層において、属性は共有(クラステーブルで定義)されるか、固有(個々のインスタンスに格納)されるかのいずれかになります。
共有属性はクラステーブルに直接定義され、メタテーブルのルックアップチェーンを通じてアクセスされます。
local Animal = {}
Animal.__index = Animal
Animal.kingdom = "Animalia" -- すべてのAnimalで共有される固有属性は、コンストラクタ内でインスタンスオブジェクト(obj)に割り当てられます。
function Animal:new(name)
local obj = {name = name} -- 各インスタンスに固有
setmetatable(obj, Animal)
return obj
end子クラスは、親の属性を継承しながら、独自の共有属性を持つことができます。
local Dog = {}
Dog.__index = Dog
setmetatable(Dog, {__index = Animal})
Dog.sound = "bark" -- すべてのDogで共有される
function Dog:new(name, breed)
local obj = Animal.new(Animal, name) -- 親のコンストラクタを呼び出す
obj.breed = breed -- 固有の子属性を追加する
setmetatable(obj, Dog)
return obj
end属性へのアクセス:
local rex = Dog:new("Rex", "German Shepherd")
-- 固有(rexに直接格納されている)
print(rex.name) -- "Rex"
print(rex.breed) -- "German Shepherd"
-- 共有(メタテーブルのルックアップにより発見される)
print(rex.sound) -- "bark" (Dogクラスから)
print(rex.kingdom) -- "Animalia" (Animalクラスから)自分で試してみよう
-- main.lua: ドラゴンを作成し、共有属性と固有属性のデモンストレーションを行います
local Dragon = require('Dragon')
-- 1体目のドラゴンの入力を読み込む (名前,色)
local input1 = io.read()
-- 2体目のドラゴンの入力を読み込む (名前,色)
local input2 = io.read()
-- TODO: 最初の入力をパースして名前と色を抽出する
-- ヒント: パターン "([^,]+),([^,]+)" を指定して string.match を使用してください
-- TODO: 2番目の入力をパースして名前と色を抽出する
-- TODO: 名前と色を指定して1体目のドラゴンを作成する
-- TODO: 名前と色を指定して2体目のドラゴンを作成する
-- TODO: 以下の順序で出力する:
-- 1. 1体目のドラゴンの名前
-- 2. 1体目のドラゴンの色
-- 3. 2体目のドラゴンの名前
-- 4. 2体目のドラゴンの色
-- 5. 1体目のドラゴンの属性 (Dragonクラスから共有)
-- 6. 2体目のドラゴンの属性 (Dragonクラスから共有)
-- 7. 1体目のドラゴンの世界 (Creatureクラスから共有)
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。