Утиная типизация
Часть раздела Object Oriented Programming путешествия по Lua на Coddy — урок 39 из 70.
Lua не важен класс объекта — ей важно только то, что объект может делать. Эта концепция называется утиной типизацией: «Если это ходит как утка и крякает как утка, то это утка». С точки зрения программирования, если у объекта есть метод, который вы вызываете, он работает.
Это означает, что совершенно не связанные классы могут использоваться вместе, если у них совпадает имя метода:
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endЭти классы не имеют отношений наследования — они полностью независимы. Тем не менее, мы можем обращаться с ними одинаково:
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- Вывод:
-- Рисование круга
-- Рисование квадрата
-- Рисование кругаЦикл не проверяет, к какому типу относится каждый объект. Он просто вызывает :draw() и полагается на то, что этот метод существует. Эта гибкость очень эффективна — вы можете добавлять новые типы фигур, вообще не изменяя код цикла. До тех пор, пока новый класс реализует :draw(), всё будет работать.
Задание
ЛегкоДавайте продемонстрируем мощь утиной типизации, создав систему уведомлений с совершенно не связанными друг с другом классами, которые можно обрабатывать одинаково!
Вы создадите три независимых класса уведомлений — ни один из них не наследуется от другого — но все они реализуют метод :send(). Это означает, что вы можете перебирать смешанную коллекцию и вызывать :send() для каждого объекта, не заботясь о его фактическом типе.
Вы организуете свой код в четырех файлах:
Email.lua: Класс с конструктором:new(recipient), который сохраняет адрес получателя письма. Его метод:send()должен выводитьEmailing {recipient}.SMS.lua: Совершенно отдельный класс с конструктором:new(phoneNumber). Его метод:send()должен выводитьTexting {phoneNumber}.PushNotification.lua: Еще один независимый класс с конструктором:new(deviceId). Его метод:send()должен выводитьPushing to {deviceId}.main.lua: Соберите все вместе! Прочитайте три входных значения, создайте по одному экземпляру каждого типа уведомлений, поместите их все в одну таблицу, затем переберите таблицу и вызовите:send()для каждого объекта.
Вы получите три входных значения:
- Получатель электронной почты (например,
alice@example.com) - Номер телефона (например,
555-1234) - ID устройства (например,
device_99)
В вашем основном файле создайте все три объекта уведомлений, сохраните их в таблицу и итерируйте по таблице, вызывая :send() для каждого. Порядок должен быть следующим: сначала Email, затем SMS, затем PushNotification.
Например, если на вход подаются bob@mail.com, 555-9876 и phone_42, вывод должен быть следующим:
Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42Обратите внимание, что вашему циклу не нужно проверять тип каждого объекта — он просто доверяет тому, что у каждого объекта в коллекции есть метод :send(). Это и есть утиная типизация в действии! Если объект может :send(), значит, это уведомление.
Шпаргалка
Lua использует утиную типизацию: если у объекта есть метод, который вы вызываете, он работает, независимо от его класса. Принцип таков: «если это ходит как утка и крякает как утка, то это утка».
Совершенно не связанные между собой классы могут использоваться взаимозаменяемо, если у них совпадает имя метода:
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endЭти классы не имеют отношений наследования, однако с ними можно обращаться одинаково:
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- Output:
-- Drawing a circle
-- Drawing a square
-- Drawing a circleЦикл не проверяет типы объектов — он просто вызывает метод и доверяет тому, что он существует. Вы можете добавлять новые типы без изменения существующего кода, если они реализуют необходимый метод.
Попробуйте сами
-- main.lua
-- Объедините все типы уведомлений и продемонстрируйте утиную типизацию
-- Импорт классов уведомлений
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')
-- Чтение входных данных
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()
-- TODO: Создайте по одному экземпляру каждого типа уведомления
-- TODO: Поместите все уведомления в одну таблицу (сначала Email, затем SMS, затем PushNotification)
-- TODO: Пройдите циклом по таблице и вызовите :send() для каждого уведомления
-- Это демонстрирует утиную типизацию — мы не проверяем тип, мы просто вызываем :send()
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур