Menu
CoddyTech

Mixins vs Inheritance

Part of the Object Oriented Programming section of Coddy's Lua journey. Lesson 53 of 70.

You've learned two ways to share behavior: inheritance creates an "is-a" relationship through the prototype chain, while mixins copy functions directly into a class. But when should you use each approach?

Use inheritance when objects share a fundamental identity. A Bird is an Animal. It makes sense for Bird to inherit core animal behaviors like :eat() or :sleep(). The relationship is permanent and defines what the object fundamentally is.

Use mixins when you want to add a capability that doesn't define the object's core identity.

Not all birds can fly: penguins and ostriches are birds, but they don't fly. Flying is a capability, not a defining trait of being a bird. A Flyable mixin lets you add flight to birds that need it, without forcing it on all of them.

The two approaches work together. Here Eagle and Penguin both inherit from Bird, and only Eagle gets the mixin:

-- Bird inherits from Animal (is-a relationship)
local Bird = {}
Bird.__index = Bird
setmetatable(Bird, {__index = Animal})

-- Eagle and Penguin are both birds
local Eagle = setmetatable({}, {__index = Bird})
Eagle.__index = Eagle
local Penguin = setmetatable({}, {__index = Bird})
Penguin.__index = Penguin

-- Flyable is a capability (mixin)
local Flyable = {}
function Flyable:fly()
    print(self.name .. " soars through the sky!")
end

-- Apply the mixin only to the birds that fly
applyMixin(Eagle, Flyable)

An eagle can call :fly(). A penguin can't: fly is not in Penguin, Bird or Animal, so penguin:fly() is an error. Applying the mixin to Bird instead would copy fly into every bird, penguins included.

challenge icon

Challenge

Easy

Let's build a small ecosystem of creatures that demonstrates when to use inheritance versus mixins! You'll create an Animal base class, a Bird that inherits from it, and a Swimmable mixin, because while all birds are animals, not all birds can swim.

You'll organize your code across four files:

  • Animal.lua: Create the base Animal class with a constructor :new(name) that stores the name. Include an :eat() method that prints {name} is eating. This represents the core identity that all animals share.
  • Swimmable.lua: Create a mixin with a :swim() method that prints {self.name} is swimming. This is a capability, not all creatures can swim, so it shouldn't be part of the inheritance chain.
  • Bird.lua: Create a Bird class that inherits from Animal. The constructor :new(name) should call the parent constructor. Include a :chirp() method that prints {name} chirps!. This is a bird-specific behavior. Don't put swimming in Bird: not every bird can swim.
  • main.lua: Write the applyMixin(target, mixin) helper here: it copies every function of the mixin into the target. Read the bird's name and whether it can swim (yes or no). Create a Bird, and if it can swim, apply the Swimmable mixin to that bird only. Then call :eat() (inherited from Animal) and :chirp() (Bird's own method), and call :swim() only if this bird has it (if bird.swim then).

You will receive two inputs:

  1. The bird's name (a string)
  2. Whether the bird can swim: yes or no

If the bird can swim, your output should be:

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

If the bird cannot swim, your output should be:

{name} is eating
{name} chirps!

For example, if the inputs are Duck and yes, the output should be:

Duck is eating
Duck chirps!
Duck is swimming

If the inputs are Sparrow and no, the output should be:

Sparrow is eating
Sparrow chirps!

This challenge shows the key distinction: Bird is an Animal (inheritance), but swimming is an optional capability (mixin). Because the mixin goes onto the swimming birds only, a bird that can't swim has no swim method at all.

Try it yourself

-- main.lua: Entry point - create a bird and demonstrate its abilities

-- Helper function to apply a mixin to a class or to a single object
local function applyMixin(target, mixin)
    -- TODO: Copy every function from mixin into target

end

local Bird = require('Bird')
local Swimmable = require('Swimmable')

-- Read inputs
local name = io.read()
local canSwimInput = io.read()

-- TODO: Create a Bird instance with the name

-- TODO: If the input is "yes", apply the Swimmable mixin to this bird only

-- TODO: Call eat() - inherited from Animal

-- TODO: Call chirp() - Bird's own method

-- TODO: Call swim() only if this bird has it
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online Lua compiler