コンストラクタの拡張
CoddyのLuaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 33/70。
子クラスが親クラスから継承する場合、親クラスが行うすべての初期化に加えて、子クラス固有の追加フィールドも子クラスのコンストラクタで初期化したいことがよくあります。コードを重複させる代わりに、親クラスのコンストラクタを直接呼び出すことができます。
この手法では、親クラス名を使用してその :new() メソッドを呼び出し、最初の引数として self を渡します。これにより、親の初期化がすべて行われたベースオブジェクトが作成され、その後、子クラス向けにカスタマイズします。
local Vehicle = {}
Vehicle.__index = Vehicle
function Vehicle:new(speed)
local obj = {speed = speed}
setmetatable(obj, Vehicle)
return obj
end
-- CarはVehicleを継承します
local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})
function Car:new(speed, brand)
local obj = Vehicle.new(Vehicle, speed) -- 親のコンストラクタを呼び出す
obj.brand = brand -- 子クラス固有のフィールドを追加
setmetatable(obj, Car) -- Carに再リンクする
return obj
end子クラスのコンストラクタにおける主要なステップに注目してください:まず、Vehicle.new(Vehicle, speed) を呼び出して、speed フィールドが既に設定されたオブジェクトを取得します。次に、子クラス固有の brand フィールドを追加します。最後に、メタテーブルを Vehicle から Car に変更することで、インスタンスが最初に子クラスのメソッドを検索するようにします。
親コンストラクタを呼び出す際にコロン構文を使用することもできます。Vehicle:new(speed) は Vehicle.new(Vehicle, speed) と同等です。どちらも Vehicle を最初の引数として渡すためです。ここではどちらの形式も正しく動作するため、より読みやすいと感じる方を自由に使用してください。
local myCar = Car:new(120, "Toyota")
print(myCar.speed) -- 出力: 120
print(myCar.brand) -- 出力: ToyotaこのパターンはコードをDRYに保ちます。共有の初期化ロジックは親に置かれ、各子は独自の属性のみを処理します。
チャレンジ
簡単子クラスが親クラスのコンストラクタを拡張する、クリーチャーの階層構造を構築しましょう!Animal親クラスとBird子クラスを作成します。Birdのコンストラクタは、独自の属性を追加する前に、Animalのコンストラクタを呼び出して共通の初期化を処理します。
コードは以下の3つのファイルに分けて構成します:
Animal.lua: 動物の名前を保存する:new(name)コンストラクタを持つ親クラスを定義します。動物の名前を出力する:describe()メソッドを含めます。__indexを使用した標準的なクラスパターンを設定し、クラスを返します。Bird.lua: Animalを継承する子クラスを定義します。Birdの:new(name, wingspan)コンストラクタは、Animal.new(Animal, name)を使用して親コンストラクタを呼び出し名前を初期化した後、オブジェクトにwingspanフィールドを追加し、最後にメタテーブルをBirdに再リンクする必要があります。名前と翼長(wingspan)の両方を特定の形式で出力する:info()メソッドを含めます。main.lua: Birdモジュールを読み込み(require)、2つの入力(名前と翼長)を読み取り、Birdのインスタンスを作成します。そして、継承された機能と子クラス固有の機能が連携して動作することを示すために、両方のメソッドを呼び出します。
以下の2つの入力を受け取ります:
- 鳥の名前
- 鳥の翼長(数値)
メインファイルで、与えられた名前と翼長を持つBirdを作成してください。その後、:describe() を呼び出し、続いて :info() を別々の行で呼び出します。
:describe() メソッド(Animalから継承)は、以下を出力する必要があります:
Animal: {name}:info() メソッド(Birdで定義)は、以下を出力する必要があります:
{name} has a wingspan of {wingspan}例えば、入力が Eagle と 200 の場合、出力は以下のようになります:
Animal: Eagle
Eagle has a wingspan of 200ここでのポイントは、Birdのコンストラクタが名前の初期化を重複して行わないことです。その作業をAnimalのコンストラクタに委譲し、鳥に固有のものだけを追加します。これにより、適切な継承チェーンを構築しながら、コードをDRY(Don't Repeat Yourself)に保つことができます!
自分で試してみよう
-- main.lua: エントリーポイント
local Bird = require('Bird')
-- 入力を読み込む
local name = io.read()
local wingspan = tonumber(io.read())
-- TODO: 与えられた name と wingspan を使用して Bird インスタンスを作成する
-- TODO: bird に対して :describe() を呼び出す (Animal から継承)
-- TODO: bird に対して :info() を呼び出す (Bird で定義)
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。