Menu
Coddy logo textTech

Утиная типизация

Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 39 из 70.

Lua не заботит class объекта. Её интересует только то, что объект может делать. Эта концепция называется duck-типизацией: «Если он ходит как duck и крякает как duck, значит, это duck». С точки зрения программирования, если у объекта есть вызываемый вами method, всё работает.

Это означает, что совершенно не связанные между собой классы можно использовать вместе, если у них одинаковое имя метода:

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(), всё просто работает.

challenge icon

Задание

Легко

Давайте продемонстрируем возможности утиковой типизации, создав систему уведомлений с совершенно независимыми classes, которые можно обрабатывать одинаковым образом!

Вы создадите три независимых classes уведомлений: ни один из них не наследуется от другого, но все они реализуют method :send(). Это означает, что вы можете пройти в цикле по смешанной коллекции и вызвать :send() для каждого элемента, не заботясь о его фактическом типе.

Вы организуете код в четырех файлах:

  • Email.lua: class с конструктором :new(recipient), который сохраняет адресата email. Его method :send() должен выводить Emailing {recipient}.
  • SMS.lua: полностью отдельный class с конструктором :new(phoneNumber). Его method :send() должен выводить Texting {phoneNumber}.
  • PushNotification.lua: еще один независимый class с конструктором :new(deviceId). Его method :send() должен выводить Pushing to {deviceId}.
  • main.lua: объединим все вместе! Считайте три входных значения, создайте one экземпляр каждого типа уведомлений, поместите их all в одну таблицу, затем пройдите по ней в цикле и вызовите :send() для каждого элемента.

Вы получите три входных значения:

  1. Адресат email (например, alice@example.com)
  2. Номер телефона (например, 555-1234)
  3. Идентификатор устройства (например, device_99)

В основном файле создайте все три объекта уведомлений, сохраните их в таблице и пройдите по таблице в цикле, вызывая :send() для каждого элемента. Порядок должен быть следующим: Email first, затем SMS, затем PushNotification.

Например, если входные значения — bob@mail.com, 555-9876 и phone_42, результат должен быть таким:

Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42

Обратите внимание: вашему циклу не нужно проверять, какой тип у каждого объекта. Он just доверяет тому, что каждый объект в коллекции имеет method :send(). Это утиковая типизация в действии! Если объект может выполнить :send(), значит, это уведомление.

Попробуйте сами

-- 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()
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Lua