Menu
Coddy logo textTech

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

Часть раздела Object Oriented Programming путешествия по Lua на Coddy — урок 53 из 70.

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

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

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

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

Эти два подхода прекрасно работают вместе:

-- 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

Задание

Легко

Давайте создадим небольшую экосистему существ, которая продемонстрирует, когда использовать наследование, а когда — примеси (mixins)! Вы создадите базовый класс 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, canSwim) должен вызывать родительский конструктор и сохранять информацию о том, умеет ли эта птица плавать. Примените примесь Swimmable к классу Bird. Добавьте метод :chirp(), который выводит {name} chirps! — это поведение, специфичное для птиц.
  • main.lua: Напишите здесь вспомогательную функцию applyMixin. Считайте входные данные для имени птицы и того, умеет ли она плавать (yes или no). Создайте экземпляр Bird, затем продемонстрируйте его способности: вызовите :eat() (унаследовано от Animal), вызовите :chirp() (собственный метод Bird) и вызывайте :swim() только в том случае, если птица умеет плавать.

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

  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 (наследование), но плавание — это необязательная способность (примесь). Примесь применяется к классу, но ваша основная логика решает, использовать ли её, основываясь на способностях конкретной птицы!

Шпаргалка

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

Наследование создает постоянную связь через цепочку прототипов:

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)

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

Попробуйте сами

-- main.lua: Точка входа - создание птицы и демонстрация её способностей

-- Вспомогательная функция для применения примеси (mixin) к классу
local function applyMixin(class, mixin)
    -- TODO: Скопировать все методы из примеси в класс
    
end

-- Применить примесь Swimmable к Bird перед подключением 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() только в том случае, если птица умеет плавать
quiz iconПроверьте себя

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

Все уроки раздела Object Oriented Programming