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")
endEstas 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írculoEl 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.
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:
- Un destinatario de correo electrónico (por ejemplo,
alice@example.com) - Un número de teléfono (por ejemplo,
555-1234) - 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_42Observa 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")
endEstas 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 circleEl 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()
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1El concepto de 'self'
Tablas con funciones'self' explícitoLa sintaxis de dos puntosPunto vs. dos puntosResumen - Punto en movimiento4Proyecto: Banco Digital
Configuración del proyectoMétodo de depósito7Polimorfismo y Sobrescritura
Sobrescritura de métodosLlamar a métodos del padreDuck TypingInterfaz comúnComprobación de tiposRepaso - Roles de empleados2Patrón de prototipo de clase
El concepto de prototipoVinculación con __indexEl constructor :new()Inicialización de atributosInstancias independientesResumen - Fábrica de coches5Sobrecarga de operadores en POO
Sumar objetosRestar objetosConcatenar objetosComparar objetos (<, >)Resumen - Matemáticas de la billetera8Encapsulamiento
Convenciones de nomenclaturaClosures para privacidadAcceso mediante ClosuresTablas de solo lecturaLógica de validaciónResumen - Bóveda segura11Patrones de diseño (Lite)
Funciones FactoryTabla SingletonPatrón IteradorObserver (Listener)Resumen - Logger Factory3Estado y Comportamiento de los Objetos
Variables de InstanciaMétodos GetterMétodos SetterPropiedades CalculadasFormateo de StringsComprobaciones de IgualdadResumen - Calificación del Estudiante6Fundamentos de la herencia
Configuración de la herenciaHerencia de métodosExtender el constructorAñadir métodos hijosCompartido vs. ÚnicoResumen: Jerarquía de formas