Menu
CoddyTech

Примеси против наследования

Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 53 из 70.

Ты узнал два способа совместного использования поведения: наследование создаёт отношение «является разновидностью» через цепочку прототипов, а примеси напрямую копируют функции в класс. Но когда следует использовать каждый из этих подходов?

Используй наследование, когда объекты имеют общую сущностную природу. Bird — это Animal. Логично, чтобы Bird наследовал основные действия животных, такие как :eat() или :sleep(). Это постоянная связь, определяющая сущность объекта.

Используй примеси, когда хочешь добавить возможность, которая не определяет основную сущность объекта.

Не все птицы умеют летать: пингвины и страусы — птицы, но они не летают. Полёт — это способность, а не определяющий признак птицы. Миксин Flyable позволяет добавить способность летать тем птицам, которым это нужно, не навязывая её всем.

Эти два подхода работают вместе. Здесь и Eagle, и Penguin наследуют от Bird, а примесь получает только Eagle:

-- Bird наследуется от Animal (отношение «является»)
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 — это возможность (примесь)
local Flyable = {}
function Flyable:fly()
    print(self.name .. " soars through the sky!")
end

-- Примените примесь только к птицам, которые летают
applyMixin(Eagle, Flyable)

Орёл может вызвать :fly(). Пингвин не может: fly нет ни в Penguin, ни в Bird, ни в Animal, поэтому penguin:fly() вызывает ошибку. Если применить примесь к Bird, то fly скопируется в каждую птицу, включая пингвинов.

challenge icon

Задание

Легко

Давайте создадим небольшую экосистему существ, которая покажет, когда использовать наследование, а когда — примеси! Вы создадите базовый класс Animal, класс Bird, который наследуется от него, и примесь Swimmable, потому что все птицы — животные, но не все птицы умеют плавать.

Вы распределите код по четырём файлам:

  • Animal.lua: Создайте базовый класс Animal с конструктором :new(name), который сохраняет имя. Добавьте метод :eat(), который выводит {name} is eating. Это общая основа, присущая всем животным.
  • Swimmable.lua: Создайте примесь с методом :swim(), который выводит {self.name} is swimming. Это способность: не все существа умеют плавать, поэтому ей не место в цепочке наследования.
  • Bird.lua: Создайте класс Bird, который наследуется от Animal. Конструктор :new(name) должен вызывать конструктор родительского класса. Добавьте метод :chirp(), который выводит {name} chirps!. Это поведение, характерное для птиц. Не добавляйте плавание в Bird: не каждая птица умеет плавать.
  • main.lua: Напишите здесь вспомогательную функцию applyMixin(target, mixin): она копирует все функции примеси в целевой объект. Считайте имя птицы и информацию о том, умеет ли она плавать (yes или no). Создайте объект Bird и, если он умеет плавать, примените примесь Swimmable только к этой птице. Затем вызовите :eat() (унаследованный от Animal) и :chirp() (собственный метод Bird), а :swim() вызывайте, только если у этой птицы есть такой метод (if bird.swim then).

Вы получите два входных значения:

  1. Имя птицы (строка)
  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: точка входа — создать птицу и продемонстрировать её способности

-- Вспомогательная функция для применения миксина к классу или отдельному объекту
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: Создать экземпляр Bird с заданным именем

-- TODO: Если входное значение — "yes", применить миксин Swimmable только к этой птице

-- TODO: Вызвать eat() — метод, унаследованный от Animal

-- TODO: Вызвать chirp() — собственный метод Bird

-- TODO: Вызвать swim(), только если у этой птицы есть такой метод
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Lua