Menu
Coddy logo textTech

Duck Typing

Parte de la sección Object Oriented Programming del Journey de Lua de Coddy — lección 39 de 70.

A Lua no le importa la clase de un objeto; solo le importa lo que el objeto puede hacer. Este concepto se llama duck typing: "Si camina como un pato y grazna como un pato, es un pato". En términos de programación, si un objeto tiene el método al que estás llamando, funciona.

Esto significa que se pueden utilizar juntas clases completamente no relacionadas, siempre que compartan el mismo nombre de método:

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

Estas clases no tienen ninguna relación de herencia: son completamente independientes. Sin embargo, podemos tratarlas de forma idéntica:

local shapes = {Circle:new(), Square:new(), Circle:new()}

for _, shape in ipairs(shapes) do
    shape:draw()
end
-- Salida:
-- Dibujando un círculo
-- Dibujando un cuadrado
-- Dibujando un círculo

El bucle no comprueba de qué tipo es cada objeto. Simplemente llama a :draw() y confía en que el método existe. Esta flexibilidad es potente: puedes añadir nuevos tipos de formas sin modificar el código del bucle en absoluto. Siempre que la nueva clase implemente :draw(), simplemente funciona.

challenge icon

Desafío

Fácil

¡Vamos a demostrar el poder del duck typing construyendo un sistema de notificaciones con clases completamente independientes que pueden procesarse de la misma manera!

Crearás tres clases de notificación independientes —ninguna de ellas hereda de la otra— pero todas implementan un método :send(). Esto significa que puedes recorrer una colección mixta y llamar a :send() en cada una sin preocuparte por su tipo real.

Organizarás tu código en cuatro archivos:

  • Email.lua: Una clase con un constructor :new(recipient) que almacena a quién va dirigido el correo electrónico. Su método :send() debe imprimir "Emailing " .. self.recipient.
  • SMS.lua: Una clase completamente separada con un constructor :new(phoneNumber). Su método :send() debe imprimir "Texting " .. self.phoneNumber.
  • PushNotification.lua: Otra clase independiente con un constructor :new(deviceId). Su método :send() debe imprimir "Pushing to " .. self.deviceId.
  • main.lua: ¡Reúne todo! Lee tres entradas, crea una instancia de cada tipo de notificación, colócalas todas en una sola tabla, luego recórrela y llama a :send() en cada una.

Recibirás tres entradas:

  1. Un destinatario de correo electrónico (por ejemplo, alice@example.com)
  2. Un número de teléfono (por ejemplo, 555-1234)
  3. Un ID de dispositivo (por ejemplo, device_99)

En tu archivo principal, crea los tres objetos de notificación, almacénalos en una tabla e itera a través de la tabla llamando a :send() en cada uno. El orden debe ser: primero Email, luego SMS y después PushNotification.

Por ejemplo, si las entradas son bob@mail.com, 555-9876 y phone_42, la salida debería ser:

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

Observa cómo tu bucle no necesita verificar de qué tipo es cada objeto; simplemente confía en que cada objeto de la colección tiene un método :send(). ¡Eso es el duck typing en acción! Si puede ejecutar :send(), es una notificación.

Hoja de referencia

Lua utiliza el duck typing (tipado de pato): si un objeto tiene el método al que estás llamando, funciona, independientemente de su clase. El principio es "si camina como un pato y grazna como un pato, entonces es un pato".

Clases completamente no relacionadas pueden usarse indistintamente siempre que compartan el mismo nombre de método:

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

Estas clases no tienen relación de herencia, pero pueden tratarse de forma idéntica:

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

El bucle no comprueba los tipos de objeto; simplemente llama al método y confía en que existe. Puedes añadir nuevos tipos sin modificar el código existente, siempre que implementen el método requerido.

Pruébalo tú mismo

-- main.lua
-- Reunir todos los tipos de notificación y demostrar el tipado de pato (duck typing)

-- Importar las clases de notificación
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')

-- Leer entradas
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()

-- TODO: Crear una instancia de cada tipo de notificación

-- TODO: Poner todas las notificaciones en una sola tabla (Email primero, luego SMS, luego PushNotification)

-- TODO: Recorrer la tabla y llamar a :send() en cada notificación
-- Esto demuestra el tipado de pato: no verificamos el tipo, solo llamamos a :send()
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming