ミックスイン vs 継承
CoddyのLuaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 53/70。
振る舞いを共有する方法を2つ学びました。継承はプロトタイプチェーンを通じて「is-a」の関係を作り、ミックスインは関数をクラスに直接コピーします。それぞれの方法は、どのようなときに使うべきでしょうか?
オブジェクトが根本的なアイデンティティを共有するときは、継承を使います。BirdはAnimalの一種です。Birdが:eat()や:sleep()のような動物の基本的な振る舞いを継承するのは理にかなっています。この関係は永続的で、オブジェクトが根本的に何であるかを定義します。
オブジェクトの中核となるアイデンティティを定義しない機能を追加したい場合は、ミックスインを使います。
すべての鳥が飛べるわけではありません。ペンギンやダチョウは鳥ですが、飛びません。飛ぶことは能力であり、鳥であることを定義する特徴ではありません。Flyableミックスインを使えば、飛ぶ必要がある鳥に飛行能力を追加でき、すべての鳥にそれを強制せずに済みます。
この2つの方法は連携して機能します。ここでは、EagleとPenguinの両方がBirdを継承し、ミックスインを受け取るのはEagleだけです。
-- BirdはAnimalを継承する(is-a関係)
local Bird = {}
Bird.__index = Bird
setmetatable(Bird, {__index = Animal})
-- EagleとPenguinはどちらも鳥である
local Eagle = setmetatable({}, {__index = Bird})
Eagle.__index = Eagle
local Penguin = setmetatable({}, {__index = Bird})
Penguin.__index = Penguin
-- Flyableは能力(mixin)である
local Flyable = {}
function Flyable:fly()
print(self.name .. " soars through the sky!")
end
-- 飛べる鳥にだけmixinを適用する
applyMixin(Eagle, Flyable)ワシは:fly()を呼び出せます。ペンギンは呼び出せません。flyはPenguin、Bird、Animalのいずれにもないため、penguin:fly()はエラーになります。代わりにミックスインをBirdに適用すると、ペンギンを含むすべての鳥にflyがコピーされます。
チャレンジ
簡単継承とミックスインをいつ使うべきかを示す、小さな生き物の生態系を作りましょう!Animal基底クラス、それを継承するBird、そしてSwimmableミックスインを作成します。すべての鳥は動物ですが、すべての鳥が泳げるわけではないからです。
コードを4つのファイルに分けて整理します。
Animal.lua:名前を保存するコンストラクター:new(name)を持つAnimal基底クラスを作成します。{name} is eatingを出力する:eat()メソッドを含めます。これは、すべての動物が共有する中核的な特性を表します。Swimmable.lua:{self.name} is swimmingを出力する:swim()メソッドを持つミックスインを作成します。これは能力であり、すべての生き物が泳げるわけではないため、継承の連鎖に含めるべきではありません。Bird.lua:Animalを継承するBirdクラスを作成します。コンストラクター:new(name)は親クラスのコンストラクターを呼び出します。{name} chirps!を出力する:chirp()メソッドを含めます。これは鳥特有の振る舞いです。泳ぐ機能をBirdに含めないでください。すべての鳥が泳げるわけではありません。main.lua:ここにapplyMixin(target, mixin)ヘルパーを記述します。このヘルパーは、ミックスインのすべての関数を対象にコピーします。鳥の名前と泳げるかどうか(yesまたはno)を読み取ります。Birdを作成し、泳げる場合は、その鳥だけにSwimmableミックスインを適用します。次に、:eat()(Animalから継承)と:chirp()(Bird独自のメソッド)を呼び出し、この鳥がswimを持っている場合に限り、:swim()を呼び出します(if bird.swim then)。
入力は2つです。
- 鳥の名前(文字列)
- 鳥が泳げるかどうか:
yesまたはno
鳥が泳げる場合、出力は次のようになります。
{name} is eating
{name} chirps!
{name} is swimming鳥が泳げない場合、出力は次のようになります。
{name} is eating
{name} chirps!たとえば、入力がDuckとyesの場合、出力は次のようになります。
Duck is eating
Duck chirps!
Duck is swimming入力がSparrowとnoの場合、出力は次のようになります。
Sparrow is eating
Sparrow chirps!この課題では、重要な違いを示します。BirdはAnimalの一種(継承)ですが、泳ぐことは任意の能力(ミックスイン)です。ミックスインは泳げる鳥にのみ追加されるため、泳げない鳥にはswimメソッドがまったくありません。
自分で試してみよう
-- main.lua: エントリーポイント - 鳥を作成し、その能力を実演する
-- クラスまたは単一のオブジェクトにmixinを適用するヘルパー関数
local function applyMixin(target, mixin)
-- TODO: mixinのすべての関数をtargetにコピーする
end
local Bird = require('Bird')
local Swimmable = require('Swimmable')
-- 入力を読み取る
local name = io.read()
local canSwimInput = io.read()
-- TODO: nameを使ってBirdのインスタンスを作成する
-- TODO: 入力が"yes"なら、この鳥だけにSwimmableのmixinを適用する
-- TODO: Animalから継承したeat()を呼び出す
-- TODO: Bird自身のメソッドchirp()を呼び出す
-- TODO: この鳥がswim()を持っている場合のみ呼び出す
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Luaオンラインコンパイラ