Menu
Coddy logo textTech

Duck Typing

Parte da seção Object Oriented Programming do Journey de Lua da Coddy — lição 39 de 70.

Lua não se importa com a classe de um objeto — ela se importa apenas com o que o objeto pode fazer. Esse conceito é chamado de duck typing: "Se ele anda como um pato e grasna como um pato, então é um pato". Em termos de programação, se um objeto tem o método que você está chamando, ele funciona.

Isso significa que classes completamente não relacionadas podem ser usadas juntas, desde que compartilhem o mesmo nome 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

Essas classes não possuem relação de herança—elas são completamente independentes. Ainda assim, podemos tratá-las de forma idêntica:

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

for _, shape in ipairs(shapes) do
    shape:draw()
end
-- Saída:
-- Desenhando um círculo
-- Desenhando um quadrado
-- Desenhando um círculo

O loop não verifica de qual tipo cada objeto é. Ele simplesmente chama :draw() e confia que o método existe. Essa flexibilidade é poderosa — você pode adicionar novos tipos de formas sem modificar o código do loop. Desde que a nova classe implemente :draw(), ele simplesmente funciona.

challenge icon

Desafio

Fácil

Vamos demonstrar o poder do duck typing construindo um sistema de notificação com classes completamente não relacionadas que podem ser processadas da mesma maneira!

Você criará três classes de notificação independentes—nenhuma delas herda da outra—mas todas implementam um método :send(). Isso significa que você pode percorrer uma coleção mista e chamar :send() em cada uma sem se preocupar com seu tipo real.

Você organizará seu código em quatro arquivos:

  • Email.lua: Uma classe com um construtor :new(recipient) que armazena para quem o e-mail será enviado. Seu método :send() deve imprimir Emailing {recipient}.
  • SMS.lua: Uma classe completamente separada com um construtor :new(phoneNumber). Seu método :send() deve imprimir Texting {phoneNumber}.
  • PushNotification.lua: Outra classe independente com um construtor :new(deviceId). Seu método :send() deve imprimir Pushing to {deviceId}.
  • main.lua: Reúna tudo! Leia três entradas, crie uma instância de cada tipo de notificação, coloque todas em uma única tabela e, em seguida, percorra a tabela chamando :send() em cada uma.

Você receberá três entradas:

  1. Um destinatário de e-mail (ex: alice@example.com)
  2. Um número de telefone (ex: 555-1234)
  3. Um ID de dispositivo (ex: device_99)

No seu arquivo principal, crie todos os três objetos de notificação, armazene-os em uma tabela e itere pela tabela chamando :send() em cada um. A ordem deve ser: Email primeiro, depois SMS e, por fim, PushNotification.

Por exemplo, se as entradas forem bob@mail.com, 555-9876, e phone_42, a saída deve ser:

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

Observe como seu loop não precisa verificar qual é o tipo de cada objeto—ele apenas confia que cada objeto na coleção possui um método :send(). Isso é o duck typing em ação! Se ele pode :send(), ele é uma notificação.

Folha de consulta

Lua usa duck typing: se um objeto tem o método que você está chamando, ele funciona, independentemente de sua classe. O princípio é "se ele anda como um pato e faz quá-quá como um pato, então é um pato."

Classes completamente não relacionadas podem ser usadas de forma intercambiável, desde que compartilhem o mesmo nome 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

Essas classes não possuem relação de herança, mas podem ser tratadas de forma idêntica:

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

for _, shape in ipairs(shapes) do
    shape:draw()
end
-- Saída:
-- Drawing a circle
-- Drawing a square
-- Drawing a circle

O loop não verifica os tipos de objeto — ele simplesmente chama o método e confia que ele existe. Você pode adicionar novos tipos sem modificar o código existente, desde que eles implementem o método necessário.

Experimente você mesmo

-- main.lua
-- Reúne todos os tipos de notificação e demonstra duck typing

-- Importa as classes de notificação
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')

-- Lê as entradas
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()

-- TODO: Criar uma instância de cada tipo de notificação

-- TODO: Colocar todas as notificações em uma única tabela (Email primeiro, depois SMS, depois PushNotification)

-- TODO: Percorrer a tabela e chamar :send() em cada notificação
-- Isso demonstra duck typing - não verificamos o tipo, apenas chamamos :send()
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Object Oriented Programming