Применение нескольких примесей
Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 52 из 70.
Один mixin добавляет один набор возможностей в class. Но реальным объектам часто нужны возможности из нескольких источников. Игровому объекту может потребоваться перемещаться и быть видимым на экране: это две отдельные задачи, которым не место в одном mixin.
Решение простое: применить несколько mixin к одному и тому же классу. Каждый mixin добавляет свои методы, и в итоге класс получает их все:
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Примени оба mixin к своему классу, используя ту же вспомогательную функцию, что и раньше:
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Важное предостережение: если два mixin определяют одно и то же имя method, второй применённый mixin перезапишет первый. Используй разные method в своих mixin, чтобы избежать неожиданного поведения.
Задание
ЛегкоДавайте создадим класс Character для игры, который объединяет несколько возможностей с помощью mixin! Ваш персонаж сможет и атаковать врагов, и исцелять себя: это два отдельных поведения, реализованных в разных mixin.
Вы организуете код в четырёх файлах:
Attacker.lua: создайте mixin с методом:attack(targetName), который выводит{self.name} attacks {targetName} for {self.damage} damage!(предполагается, что у объекта есть поляnameиdamage)Healer.lua: создайте mixin с методом:heal(amount), который добавляет значение amount кself.healthи выводит{self.name} heals for {amount}. Health: {newHealth}Character.lua: создайте класс Character с конструктором:new(name, health, damage), который сохраняет все три атрибута. Примените оба mixin — Attacker и Healer, чтобы каждый персонаж мог атаковать и исцелять себя. Добавьте метод:status(), который выводит{name} - Health: {health}, Damage: {damage}main.lua: напишите здесь вспомогательную функциюapplyMixin. Считайте входные данные: имя персонажа, его здоровье, урон, цель атаки и количество исцеления. Создайте Character, выведите его состояние, заставьте его атаковать цель, исцелить себя, а затем снова выведите его состояние.
Вы получите пять входных данных:
- Имя Character (строка)
- Начальное здоровье (число)
- Значение урона (число)
- Имя цели для атаки (строка)
- Количество исцеления (число)
В выводе должно быть показано, как персонаж использует обе способности mixin:
{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Каждый mixin добавляет свой метод в класс Character. Персонаж не наследуется ни от одного из mixin. Он просто получает их возможности через функцию applyMixin. В этом и заключается сила объединения нескольких mixin!
Попробуйте сами
-- Основной файл
-- TODO: Напишите вспомогательную функцию applyMixin
-- Эта функция должна копировать все методы из таблицы mixin в целевую таблицу
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 с заданными name, health и damage
-- 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