Примеси против наследования
Часть раздела 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)Этот паттерн дает вам лучшее из обоих миров: четкую иерархию классов для общей идентичности, а также гибкие возможности, которые можно подмешивать там, где это необходимо.
Задание
ЛегкоДавайте создадим небольшую экосистему существ, которая продемонстрирует, когда использовать наследование, а когда — примеси (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()только в том случае, если птица умеет плавать.
Вы получите два входных значения:
- Имя птицы (строка)
- Умеет ли птица плавать:
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() только в том случае, если птица умеет плавать
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур9Композиция и примеси
Отношение "Has-A"ДелегированиеПростые примесиПрименение нескольких примесейПримеси против наследованияИтоги — Сборка робота