ミックスイン vs 継承
CoddyのLuaジャーニー「Object Oriented Programming」セクションの一部 — レッスン 53/70。
振る舞いを共有するための2つの方法を学びました。継承(inheritance)はプロトタイプチェーンを通じて「is-a」関係を作成し、一方でミックスイン(mixins)は関数をクラスに直接コピーします。しかし、それぞれの手法はどのような時に使うべきでしょうか?
オブジェクトが根本的なアイデンティティを共有している場合は、継承を使用します。Bird は Animal であるため、Bird が :eat() や :sleep() といった動物の核となる振る舞いを継承するのは理にかなっています。この関係は永続的であり、そのオブジェクトが根本的に何であるかを定義します。
オブジェクトの核となるアイデンティティを定義しない機能を追加したい場合には、mixinsを使用してください。
すべての鳥が飛べるわけではありません。ペンギンやダチョウは鳥ですが、飛びません。飛ぶことは一つの能力であり、鳥であることの定義的な特徴ではありません。Flyable ミックスインを使用すると、それを必要とする鳥に飛行能力を追加でき、すべての鳥にそれを強制することはありません。
これら2つのアプローチは、非常にうまく組み合わさります:
-- BirdはAnimalを継承します (is-a関係)
local Bird = {}
Bird.__index = Bird
setmetatable(Bird, {__index = Animal})
-- Flyableは能力(ミックスイン)です
local Flyable = {}
function Flyable:fly()
print(self.name .. " soars through the sky!")
end
-- 飛ぶことができる鳥にのみミックスインを適用します
applyMixin(Bird, Flyable)このパターンは、共通のアイデンティティのための明確なクラス階層と、必要に応じて組み込める柔軟な機能という、両方の利点を兼ね備えています。
チャレンジ
簡単継承とミックスインをいつ使うべきかを示す、生き物たちの小さなエコシステムを構築しましょう!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, canSwim)は親のコンストラクタを呼び出し、その鳥が泳げるかどうかを保存する必要があります。Bird クラスに Swimmable ミックスインを適用してください。{name} chirps!と出力する:chirp()メソッドを含めます。これは鳥特有の振る舞いです。main.lua: ここにapplyMixinヘルパー関数を記述します。鳥の名前と、泳げるかどうか(yesまたはno)の入力を読み取ります。Bird インスタンスを作成し、その能力を実演します。:eat()(Animal から継承)、:chirp()(Bird 自身のメソッド)を呼び出し、鳥が泳げる場合にのみ:swim()を呼び出します。
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 「である」(継承)一方で、泳ぐことはオプションの「能力」(ミックスイン)です。ミックスインはクラスに適用されますが、メインのロジックは個々の鳥の能力に基づいてそれを使用するかどうかを決定します!
チートシート
オブジェクトが根本的なアイデンティティ(「is-a」関係)を共有している場合は、継承を使用します。オブジェクトの核となるアイデンティティを定義しない機能を追加する場合は、ミックスインを使用します。
継承は、プロトタイプチェーンを通じて永続的な関係を作成します:
local Bird = {}
Bird.__index = Bird
setmetatable(Bird, {__index = Animal})ミックスインは、選択的に適用できるオプションの機能を追加します:
local Flyable = {}
function Flyable:fly()
print(self.name .. " soars through the sky!")
end
applyMixin(Bird, Flyable)これら2つのアプローチは連携して機能します。共有されたアイデンティティには継承を使用し、一部のインスタンスのみが必要とする柔軟な機能にはミックスインを使用します。
自分で試してみよう
-- main.lua: エントリポイント - 鳥を作成し、その能力を実演します
-- クラスにミックスインを適用するためのヘルパー関数
local function applyMixin(class, mixin)
-- TODO: ミックスインからクラスへすべてのメソッドをコピーする
end
-- Birdをrequireする前に、SwimmableミックスインをBirdに適用する
-- 注意: これはBird.lua内、またはここで行う必要があるかもしれません
local Bird = require('Bird')
local Swimmable = require('Swimmable')
-- Birdクラスにミックスインを適用
applyMixin(Bird, Swimmable)
-- 入力を読み込む
local name = io.read()
local canSwimInput = io.read()
-- TODO: 入力("yes" または "no")に基づいて、鳥が泳げるかどうかを判断する
-- TODO: 名前と水泳能力を持つBirdインスタンスを作成する
-- TODO: eat() を呼び出す - Animalから継承
-- TODO: chirp() を呼び出す - Bird自身のメソッド
-- TODO: 鳥が泳げる場合のみ swim() を呼び出す
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。