Menu
Coddy logo textTech

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)
end

Ahora, 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] Iniciando

La 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.

challenge icon

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 imprimir Charging {amount} to card {cardNumber}.
  • BankTransfer.lua: Una clase de pago completamente independiente con un constructor :new(accountId). Su método :process(amount) debe imprimir Transferring {amount} from account {accountId}.
  • main.lua: Crea una función llamada handlePayment(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:

  1. Un número de tarjeta de crédito (por ejemplo, 4532-1234-5678)
  2. Un ID de cuenta bancaria (por ejemplo, ACC-9876)
  3. 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-5555

La 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
quiz iconPonte a prueba

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

Practica por tu cuenta: Compilador de Lua online