Примеси против наследования
Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 53 из 70.
Вы изучили два способа совместного использования поведения: наследование создаёт отношение «является» через цепочку прототипов, а примеси напрямую копируют функции в класс. Но когда следует использовать каждый из этих подходов?
Используй наследование, когда объекты имеют общую фундаментальную сущность. Bird является Animal. Имеет смысл, чтобы Bird наследовал основные поведенческие характеристики животных, такие как :eat() или :sleep(). Это постоянная связь, определяющая, чем объект является по своей сути.
Используйте примеси, если хотите добавить возможность, которая не определяет основную сущность объекта.
Не все птицы умеют летать: пингвины и страусы — птицы, но они не летают. Полёт — это capability, а не определяющий признак птицы. Миксин Flyable позволяет добавить способность к полёту птицам, которым она нужна, не навязывая её всем.
Два подхода прекрасно работают вместе:
-- Bird наследует от Animal (отношение is-a)
local Bird = {}
Bird.__index = Bird
setmetatable(Bird, {__index = Animal})
-- Flyable — это способность (миксин)
local Flyable = {}
function Flyable:fly()
print(self.name .. " soars through the sky!")
end
-- Применить миксин только к птицам, которые могут летать
applyMixin(Bird, Flyable)Этот шаблон даёт вам лучшее из обоих миров: чёткую иерархию классов для общей идентичности, а также гибкие возможности, которые можно добавлять по мере необходимости.
Задание
ЛегкоДавайте создадим небольшую экосистему существ, которая демонстрирует, когда использовать наследование, а когда — примеси! Вы создадите базовый класс 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 (наследование), а плавание — это необязательная способность (примесь). Примесь применяется к классу, но основная логика решает, использовать ли её, исходя из способностей конкретной птицы!
Попробуйте сами
-- main.lua: Точка входа - создать птицу и продемонстрировать её способности
-- Вспомогательная функция для применения миксина к классу
local function applyMixin(class, mixin)
-- TODO: Скопировать все методы из mixin в class
end
-- Применить миксин Swimmable к Bird перед require Bird
-- Note: Возможно, вам нужно обработать это в Bird.lua или здесь
local Bird = require('Bird')
local Swimmable = require('Swimmable')
-- Применить mixin к классу 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() только если птица может плавать
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур9Композиция и примеси
Отношение "Has-A"ДелегированиеПростые примесиПрименение нескольких примесейПримеси против наследованияИтоги — Сборка роботаПотренируйтесь самостоятельно: Онлайн-компилятор Lua