Interfaz común
Parte de la sección Object Oriented Programming del Journey de Lua de Coddy — lección 40 de 70.
El duck typing nos mostró que las clases no relacionadas pueden trabajar juntas si comparten nombres de métodos. Esta idea conduce a un concepto de diseño potente: la interfaz común. Cuando diseñas intencionalmente múltiples clases para que tengan las mismas firmas de método, creas componentes intercambiables.
Imagina que estás construyendo un sistema de registro. Es posible que desees enviar mensajes a diferentes destinos: un archivo, la pantalla o incluso una red. En lugar de escribir código por separado para cada uno, diseñas todas tus clases de salida para que implementen el mismo 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)
endAhora cualquier código que necesite mostrar mensajes puede aceptar cualquier tipo:
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] IniciandoLa función logMessage no sabe ni le importa qué clase recibe. Solo espera un método :write(). Esto hace que tu código sea flexible: puedes añadir nuevos tipos de salida más adelante sin cambiar las funciones existentes.
Desafío
Fácil¡Vamos a construir un sistema de procesamiento de pagos donde se puedan usar diferentes métodos de pago indistintamente! Crearás dos clases de pago completamente independientes que comparten una interfaz común—un método :process(amount)—permitiendo que cualquier código que maneje pagos funcione con cualquiera de los dos tipos sin notar la diferencia.
Organizarás tu código en tres archivos:
CreditCard.lua: Una clase de pago con un constructor:new(cardNumber)que almacena el número de tarjeta. Su método:process(amount)debe imprimirCharging {amount} to card {cardNumber}.BankTransfer.lua: Una clase de pago completamente separada con un constructor:new(accountId). Su método:process(amount)debe imprimirTransferring {amount} from account {accountId}.main.lua: Crea una función llamadahandlePayment(paymentMethod, amount)que acepte cualquier objeto de pago y un monto, y luego llame a:process(amount)en él. Esta función no necesita saber si está tratando con una tarjeta de crédito o una transferencia bancaria—solo espera que el objeto tenga un método:process().
Recibirás tres entradas:
- Un número de tarjeta de crédito (por ejemplo,
4532-1234-5678) - Un ID de cuenta bancaria (por ejemplo,
ACC-9876) - Un monto de pago (por ejemplo,
150)
En tu archivo principal, crea una instancia de cada tipo de pago utilizando las dos primeras entradas. Luego, usa tu función handlePayment para procesar el monto dado a través de ambos métodos de pago—primero la tarjeta de crédito, luego la transferencia bancaria.
Por ejemplo, si las entradas son 1111-2222-3333, ACC-5555, y 75, la salida debería ser:
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555La belleza de este diseño es que tu función handlePayment funciona con cualquier objeto que implemente :process(). ¡Podrías añadir una clase PayPal mañana y, mientras tenga un método :process(amount), funcionaría con tu función existente sin ningún cambio!
Hoja de referencia
Una interfaz común es un patrón de diseño donde múltiples clases no relacionadas implementan intencionalmente las mismas firmas de método, haciéndolas intercambiables.
Al diseñar clases para que compartan nombres de métodos, puedes escribir funciones que funcionen con cualquier objeto que implemente esa interfaz:
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)
endLas funciones pueden aceptar cualquier objeto que implemente el 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 upLa función logMessage no necesita saber qué clase recibe; solo espera un método :write(). Esto hace que el código sea flexible y permite añadir nuevos tipos sin modificar las funciones existentes.
Pruébalo tú mismo
-- Importar las clases de pago
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')
-- Leer las entradas
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())
-- TODO: Crear la función handlePayment que acepte cualquier objeto de pago
-- y un monto, luego llame a :process(amount) en él
-- TODO: Crear instancias de CreditCard y BankTransfer usando las entradas
-- TODO: Usar handlePayment para procesar el monto a través de ambos métodos de pago
-- Primero la tarjeta de crédito, luego la transferencia bancaria
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