Делегирование
Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 50 из 70.
Хранение объекта внутри другого объекта — это только первый шаг. Настоящая сила композиции проявляется в delegation, когда внешний объект перенаправляет вызовы методов своим внутренним компонентам.
Подумай о запуске car. Ты не забираешься под капот и вручную не вращаешь engine. Вместо этого ты поворачиваешь key, а car handles всё остальное внутри.
В коде это означает, что класс Car предоставляет простой метод :start(), который делегирует фактическую работу двигателю:
function Car:start()
print("Turning the key...")
self.engine:run() -- Делегировать внутреннему объекту
endМашине не нужно знать, как работает двигатель. Она просто говорит двигателю выполнять свою работу. Это позволяет разделить обязанности. Класс Engine отвечает за логику двигателя, а Car координирует компоненты:
function Engine:run()
print("Engine running at " .. self.horsepower .. " HP")
end
local engine = Engine:new(250)
local car = Car:new("Coupe", engine)
car:start()
-- Вывод:
-- Поворачиваю ключ...
-- Двигатель работает на 250 HPДелегирование создаёт чистый интерфейс для пользователей вашего class. Они взаимодействуют с car, а не с его внутренними частями. Если позже вы замените реализацию engine на другую, публичный интерфейс car останется прежним: изменится только внутреннее делегирование.
Задание
ЛегкоДавайте создадим систему музыкального проигрывателя, демонстрирующую делегирование! Вы создадите компонент Speaker и MusicPlayer, который его содержит. player не будет напрямую обрабатывать sound output, вместо этого он делегирует эту ответственность своему внутреннему speaker.
Вы организуете код в трёх файлах:
Speaker.lua: Create класс Speaker с конструктором:new(volume), который сохраняет уровень громкости. Добавьте:playSound(trackName)method, который prints:Playing "{trackName}" at volume {volume}MusicPlayer.lua: Create класс MusicPlayer, чей конструктор:new(brand, speaker)принимает название brand и объект Speaker. player сохраняет speaker внутри себя. Добавьте следующие методы::play(trackName): prints{brand} player starting..., затем delegates к method:playSound()speaker:stop(): prints{brand} player stopped
main.lua: Bring everything together! Считайте inputs для громкости speaker, brand player и названия трека. Сначала Create Speaker, затем передайте его конструктору MusicPlayer. Вызовите method:play()player с названием трека, затем вызовите:stop().
Вы получите три inputs:
- Громкость speaker (число)
- brand музыкального player (строка)
- Название трека для воспроизведения (строка)
Ваш output должен показывать, как player координирует работу со своим speaker:
{brand} player starting...
Playing "{trackName}" at volume {volume}
{brand} player stoppedНапример, если inputs — это 75, SoundMax и Summer Vibes, output должен быть таким:
SoundMax player starting...
Playing "Summer Vibes" at volume 75
SoundMax player stoppedОбратите внимание, что MusicPlayer не знает, как создавать sound. Он просто сообщает своему speaker, что нужно обработать эту часть. Это делегирование в действии: outer object предоставляет чистый интерфейс, а inner component выполняет специализированную работу!
Попробуйте сами
-- main.lua
-- Собери всё вместе!
local Speaker = require('Speaker')
local MusicPlayer = require('MusicPlayer')
-- Читаем входные данные
local volume = tonumber(io.read())
local brand = io.read()
local trackName = io.read()
-- TODO: Создай Speaker с заданным volume
-- TODO: Создай MusicPlayer с brand и speaker
-- TODO: Вызови метод play у player с track name
-- TODO: Вызови метод stop у player
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур9Композиция и примеси
Отношение "Has-A"ДелегированиеПростые примесиПрименение нескольких примесейПримеси против наследованияИтоги — Сборка роботаПотренируйтесь самостоятельно: Онлайн-компилятор Lua