Interfaz común
Parte de la sección Programación Orientada a Objetos 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 poderoso: la interfaz común. Cuando diseñas intencionalmente varias clases para que tengan las mismas firmas de métodos, creas componentes intercambiables.
Imagina que estás creando un sistema de registro. Es posible que quieras enviar mensajes a distintos destinos: un archivo, la pantalla o incluso una red. En lugar de escribir código 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 cualquiera de los 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] 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¡Construyamos un sistema de procesamiento de pagos en el que se puedan usar distintos métodos de pago indistintamente! Crearás dos clases de pago completamente no relacionadas que comparten una interfaz común, un método :process(amount), lo que permite que cualquier código que gestione pagos funcione con cualquiera de los dos tipos sin conocer 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 independiente 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 importe, y después llame a:process(amount)en él. Esta función no necesita saber si está trabajando 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 importe de pago (por ejemplo,
150)
En tu archivo principal, crea una instancia de cada tipo de pago usando las dos primeras entradas. Después, usa tu función handlePayment para procesar el importe indicado mediante ambos métodos de pago: primero la tarjeta de crédito y después 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 ventaja 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, siempre que tenga un método :process(amount), funcionaría con tu función existente sin ningún cambio.
Pruébalo tú mismo
-- Importa las clases de pago
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')
-- Lee las entradas
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())
-- TODO: Crea la función handlePayment que acepta cualquier objeto de pago
-- y un amount, luego llama a :process(amount) en él
-- TODO: Crea instancias de CreditCard y BankTransfer usando las entradas
-- TODO: Usa handlePayment para procesar el amount 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 Programación Orientada a Objetos
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 formasPractica por tu cuenta: Compilador de Lua online