Применение нескольких примесей
Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 52 из 70.
Один миксин добавляет классу один набор поведения. Но реальным объектам часто нужны возможности из нескольких источников. Игровому объекту может понадобиться перемещаться и быть видимым на экране: это две отдельные задачи, которым не место в одном миксине.
Решение простое: применить несколько примесей к одному и тому же классу. Каждая примесь добавляет свои методы, и в итоге класс получает их все:
local Movable = {}
function Movable:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end
local Visible = {}
function Visible:show()
print("Showing at " .. self.x .. ", " .. self.y)
end
function Visible:hide()
print("Hidden")
endПримените оба примиксина к своему классу, используя ту же вспомогательную функцию, что и раньше:
local function applyMixin(class, mixin)
for key, value in pairs(mixin) do
class[key] = value
end
end
local GameObject = {}
GameObject.__index = GameObject
applyMixin(GameObject, Movable)
applyMixin(GameObject, Visible)Теперь экземпляры GameObject могут и перемещаться, и отображать себя:
local obj = setmetatable({x = 0, y = 0}, GameObject)
obj:move(5, 3)
obj:show() -- Показываем в точке 5, 3Одно предостережение: если два миксина определяют метод с одинаковым именем, второй применённый миксин перезапишет первый. Используй для методов миксинов разные имена, чтобы избежать неожиданного поведения.
Задание
ЛегкоДавай создадим класс Character для игры, который объединяет несколько возможностей с помощью миксинов! Твой персонаж сможет и атаковать врагов, и лечить себя: это два отдельных поведения, предоставляемых разными миксинами.
Ты организуешь код в четырёх файлах:
Attacker.lua: создай миксин с методом:attack(targetName), который выводит{self.name} attacks {targetName} for {self.damage} damage!(предполагается, что у объекта есть поляnameиdamage)Healer.lua: создай миксин с методом:heal(amount), который прибавляет amount кself.healthи выводит{self.name} heals for {amount}. Health: {newHealth}Character.lua: создай класс Character с конструктором:new(name, health, damage), который сохраняет все три атрибута. Примени миксины Attacker и Healer, чтобы каждый персонаж мог атаковать и лечиться. Добавь метод:status(), который выводит{name} - Health: {health}, Damage: {damage}main.lua: напиши здесь вспомогательную функциюapplyMixin. Считай имя персонажа, его здоровье и урон, цель для атаки и количество здоровья для восстановления. Создай Character, покажи его состояние, атакуй цель, вылечи персонажа, а затем снова покажи его состояние.
На вход будут поданы пять значений:
- Имя персонажа (строка)
- Начальное здоровье (число)
- Значение урона (число)
- Имя цели для атаки (строка)
- Количество здоровья для восстановления (число)
В выводе должно быть показано, как персонаж использует возможности обоих миксинов:
{name} - Health: {health}, Damage: {damage}
{name} attacks {target} for {damage} damage!
{name} heals for {healAmount}. Health: {newHealth}
{name} - Health: {newHealth}, Damage: {damage}Например, если на вход поданы Warrior, 100, 25, Goblin и 30, вывод должен быть таким:
Warrior - Health: 100, Damage: 25
Warrior attacks Goblin for 25 damage!
Warrior heals for 30. Health: 130
Warrior - Health: 130, Damage: 25Каждый миксин добавляет свой метод в класс Character. Персонаж не наследует ни один из миксинов. Он просто получает их возможности с помощью функции applyMixin. В этом и заключается сила объединения нескольких миксинов!
Попробуйте сами
-- Основной файл
-- TODO: Написать вспомогательную функцию applyMixin
-- Эта функция должна копировать все методы из таблицы mixin в таблицу target
local function applyMixin(target, mixin)
-- TODO: Реализовать применение mixin
end
-- Сделать applyMixin доступной глобально, чтобы Character.lua мог её использовать
_G.applyMixin = applyMixin
local Character = require('Character')
-- Считать входные данные
local name = io.read()
local health = tonumber(io.read())
local damage = tonumber(io.read())
local targetName = io.read()
local healAmount = tonumber(io.read())
-- TODO: Создать Character с заданными именем, здоровьем и уроном
-- TODO: Показать состояние персонажа
-- TODO: Сделать так, чтобы персонаж атаковал цель
-- TODO: Сделать так, чтобы персонаж исцелил себя
-- TODO: Снова показать состояние персонажа
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур9Композиция и примеси
Отношение "Has-A"ДелегированиеПростые примесиПрименение нескольких примесейПримеси против наследованияИтоги — Сборка роботаПотренируйтесь самостоятельно: Онлайн-компилятор Lua