Interface Comum
Parte da seção Object Oriented Programming do Journey de Lua da Coddy — lição 40 de 70.
O Duck typing nos mostrou que classes não relacionadas podem trabalhar juntas se compartilharem nomes de métodos. Essa ideia leva a um conceito de design poderoso: a interface comum. Quando você projeta intencionalmente várias classes para terem as mesmas assinaturas de método, você cria componentes intercambiáveis.
Imagine que você está construindo um sistema de log. Você pode querer enviar mensagens para diferentes destinos — um arquivo, a tela ou até mesmo uma rede. Em vez de escrever um código separado para cada um, você projeta todas as suas classes de saída para implementar o mesmo método :write():
local LogFile = {}
LogFile.__index = LogFile
function LogFile:new(filename)
local obj = {filename = filename}
setmetatable(obj, LogFile)
return obj
end
function LogFile:write(message)
print("[FILE:" .. self.filename .. "] " .. message)
end
local Screen = {}
Screen.__index = Screen
function Screen:new()
local obj = {}
setmetatable(obj, Screen)
return obj
end
function Screen:write(message)
print("[SCREEN] " .. message)
endAgora, qualquer código que precise exibir mensagens pode aceitar qualquer um dos tipos:
local function logMessage(output, msg)
output:write(msg)
end
local file = LogFile:new("app.log")
local screen = Screen:new()
logMessage(file, "Starting up") -- [FILE:app.log] Iniciando
logMessage(screen, "Starting up") -- [SCREEN] IniciandoA função logMessage não sabe ou se importa com qual classe recebe. Ela apenas espera um método :write(). Isso torna seu código flexível — você pode adicionar novos tipos de saída mais tarde sem alterar as funções existentes.
Desafio
FácilVamos construir um sistema de processamento de pagamentos onde diferentes métodos de pagamento podem ser usados de forma intercambiável! Você criará duas classes de pagamento completamente não relacionadas que compartilham uma interface comum—um método :process(amount)—permitindo que qualquer código que manipule pagamentos funcione com qualquer um dos tipos sem saber a diferença.
Você organizará seu código em três arquivos:
CreditCard.lua: Uma classe de pagamento com um construtor:new(cardNumber)que armazena o número do cartão. Seu método:process(amount)deve imprimirCharging {amount} to card {cardNumber}.BankTransfer.lua: Uma classe de pagamento completamente separada com um construtor:new(accountId). Seu método:process(amount)deve imprimirTransferring {amount} from account {accountId}.main.lua: Crie uma função chamadahandlePayment(paymentMethod, amount)que aceite qualquer objeto de pagamento e um valor, e então chame:process(amount)nele. Esta função não precisa saber se está lidando com um cartão de crédito ou transferência bancária—ela apenas espera que o objeto tenha um método:process().
Você receberá três entradas:
- Um número de cartão de crédito (ex:
4532-1234-5678) - Um ID de conta bancária (ex:
ACC-9876) - Um valor de pagamento (ex:
150)
No seu arquivo principal, crie uma instância de cada tipo de pagamento usando as duas primeiras entradas. Em seguida, use sua função handlePayment para processar o valor fornecido através de ambos os métodos de pagamento—primeiro o cartão de crédito, depois a transferência bancária.
Por exemplo, se as entradas forem 1111-2222-3333, ACC-5555 e 75, a saída deve ser:
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555A beleza deste design é que sua função handlePayment funciona com qualquer objeto que implemente :process(). Você poderia adicionar uma classe PayPal amanhã e, desde que ela tenha um método :process(amount), ela funcionaria com sua função existente sem nenhuma alteração!
Folha de consulta
Uma interface comum é um padrão de design onde múltiplas classes não relacionadas implementam intencionalmente as mesmas assinaturas de método, tornando-as intercambiáveis.
Ao projetar classes para compartilhar nomes de métodos, você pode escrever funções que funcionam com qualquer objeto que implemente essa interface:
local LogFile = {}
LogFile.__index = LogFile
function LogFile:new(filename)
local obj = {filename = filename}
setmetatable(obj, LogFile)
return obj
end
function LogFile:write(message)
print("[FILE:" .. self.filename .. "] " .. message)
end
local Screen = {}
Screen.__index = Screen
function Screen:new()
local obj = {}
setmetatable(obj, Screen)
return obj
end
function Screen:write(message)
print("[SCREEN] " .. message)
endFunções podem aceitar qualquer objeto que implemente o método esperado:
local function logMessage(output, msg)
output:write(msg)
end
local file = LogFile:new("app.log")
local screen = Screen:new()
logMessage(file, "Starting up") -- [FILE:app.log] Starting up
logMessage(screen, "Starting up") -- [SCREEN] Starting upA função logMessage não precisa saber qual classe ela recebe — ela apenas espera um método :write(). Isso torna o código flexível e permite adicionar novos tipos sem modificar as funções existentes.
Experimente você mesmo
-- Importar as classes de pagamento
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')
-- Ler as entradas
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())
-- TODO: Criar a função handlePayment que aceita qualquer objeto de pagamento
-- e um valor, e então chama :process(amount) nele
-- TODO: Criar instâncias de CreditCard e BankTransfer usando as entradas
-- TODO: Usar handlePayment para processar o valor através de ambos os métodos de pagamento
-- Primeiro o cartão de crédito, depois a transferência bancária
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