Menu
Coddy logo textTech

ミックスイン vs 継承

CoddyのLuaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 53/70。

動作を共有する2つの方法を学びました。継承はプロトタイプチェーンを通じて「is-a」関係を作成し、一方、mixinsは関数をクラスに直接コピーします。しかし、それぞれのアプローチはいつ使うべきでしょうか?

オブジェクトが基本的なアイデンティティを共有している場合は、継承を使用します。BirdAnimal一種です。Birdが:eat():sleep()のような動物の中核的な振る舞いを継承するのは理にかなっています。この関係は永続的で、オブジェクトが根本的に何であるかを定義します。

オブジェクトの中核となるアイデンティティを定義せずに能力を追加したい場合は、mixinsを使用します。

すべての鳥が飛べるわけではありません。ペンギンやダチョウは鳥ですが、飛びません。飛ぶことは鳥であることを定義する特徴ではなく、capabilityです。Flyableミックスインを使えば、すべての鳥に飛行能力を強制することなく、必要な鳥に飛行能力を追加できます。

2つのアプローチは見事に連携します:

-- Bird は Animal を継承する(is-a 関係)
local Bird = {}
Bird.__index = Bird
setmetatable(Bird, {__index = Animal})

-- Flyable は能力である(mixin)
local Flyable = {}
function Flyable:fly()
    print(self.name .. " soars through the sky!")
end

-- 飛べる鳥にのみ mixin を適用する
applyMixin(Bird, Flyable)

このパターンは、両方の利点を兼ね備えています。共有される同一性を明確に表現するクラス階層と、必要な場所で組み合わせられる柔軟な機能です。

challenge icon

チャレンジ

簡単

継承とミックスインのどちらを使うべきかを示す、小さな生態系を生き物たちで作ってみましょう!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)では、親のコンストラクターを呼び出し、この鳥が泳げるかどうかを保存します。SwimmableミックスインをBirdクラスに適用します。{name} chirps!を出力する:chirp()メソッドを含めます。これは鳥固有の振る舞いです。
  • main.lua:ここでapplyMixinヘルパー関数を記述します。鳥の名前と泳げるかどうかの入力(yesまたはno)を読み取ります。Birdインスタンスを作成し、その能力を示します。:eat()(Animalから継承)を呼び出し、:chirp()(Bird独自のメソッド)を呼び出し、鳥が泳げる場合にのみ:swim()を呼び出します。

2つの入力を受け取ります。

  1. 鳥の名前(文字列)
  2. 鳥が泳げるかどうか:yesまたはno

鳥が泳げる場合、出力は次のようになります。

{name} is eating
{name} chirps!
{name} is swimming

鳥が泳げない場合、出力は次のようになります。

{name} is eating
{name} chirps!

たとえば、入力がDuckyesの場合、出力は次のようになります。

Duck is eating
Duck chirps!
Duck is swimming

入力がSparrownoの場合、出力は次のようになります。

Sparrow is eating
Sparrow chirps!

このチャレンジでは、重要な違いが示されています。BirdはAnimalである(継承)のに対し、泳ぐことは任意のcapability(ミックスイン)です。ミックスインはクラスに適用されますが、それを使うかどうかは、個々の鳥の能力に基づいてmainのロジックが判断します。

自分で試してみよう

-- main.lua: エントリーポイント - 鳥を作成し、その能力を実演する

-- ミックスインをクラスに適用するヘルパー関数
local function applyMixin(class, mixin)
    -- TODO: ミックスインからクラスへすべてのメソッドをコピーする
    
end

-- Bird を require する前に Swimmable ミックスインを Bird に適用する
-- Note: これを 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() を呼び出す
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Luaオンラインコンパイラ