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")
endEssas 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írculoO 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.
Desafio
FácilVamos 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 imprimirEmailing {recipient}.SMS.lua: Uma classe completamente separada com um construtor:new(phoneNumber). Seu método:send()deve imprimirTexting {phoneNumber}.PushNotification.lua: Outra classe independente com um construtor:new(deviceId). Seu método:send()deve imprimirPushing 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:
- Um destinatário de e-mail (ex:
alice@example.com) - Um número de telefone (ex:
555-1234) - 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_42Observe 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")
endEssas 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 circleO 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()
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
1O Conceito 'Self'
Tabelas com Funções'self' ExplícitoA Sintaxe de Dois-pontosPonto vs Dois-pontosRecapitulação - Movendo o Ponto4Projeto: Banco Digital
Configuração do ProjetoMétodo de Depósito7Polimorfismo e Sobrescrita
Sobrescrita de MétodosChamando Métodos da Classe PaiDuck TypingInterface ComumVerificando o TipoRecapitulação - Papéis de Funcionários10Projeto: Gerenciador de Formas
Configuração do ProjetoClasse Retângulo2Padrão de Protótipo de Classe
O Conceito de ProtótipoVinculando com __indexO Construtor :new()Inicializando AtributosInstâncias IndependentesRecapitulação - Fábrica de Carros5Sobrecarga de Operadores em POO
Somando ObjetosSubtraindo ObjetosConcatenando ObjetosComparando Objetos (<, >)Recapitulação - Matemática de Carteira8Encapsulamento
Convenções de NomenclaturaClosures para PrivacidadeAcesso via ClosuresTabelas Somente LeituraLógica de ValidaçãoRecapitulação - Cofre Seguro11Padrões de Projeto (Lite)
Funções FactoryTabela SingletonPadrão IteratorObserver (Listener)Recapitulação - Logger Factory3Estado e Comportamento do Objeto
Variáveis de InstânciaMétodos GetterMétodos SetterPropriedades CalculadasFormatação de StringsVerificações de IgualdadeRecapitulação - Nota do Aluno6Fundamentos de Herança
Configurando a HerançaHerdando MétodosEstendendo o ConstrutorAdicionando Métodos FilhosCompartilhado vs ÚnicoRecapitulação - Hierarquia de Formas