Interfaccia comune
Fa parte della sezione Programmazione orientata agli oggetti del percorso Lua di Coddy. Lezione 40 di 70.
Il duck typing ci ha mostrato che classi non correlate possono collaborare se condividono i nomi dei metodi. Questa idea porta a un potente concetto di progettazione: l’interfaccia comune. Quando progetti intenzionalmente più classi affinché abbiano le stesse firme dei metodi, crei componenti intercambiabili.
Immagina di creare un sistema di registrazione. Potresti voler inviare messaggi a destinazioni diverse: un file, lo schermo o persino una rete. Invece di scrivere codice separato per ognuna, progetti tutte le tue classi di output in modo che implementino lo stesso metodo :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)
endOra qualsiasi codice che deve visualizzare messaggi può accettare entrambi i tipi:
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 funzione logMessage non sa né le importa quale classe riceve. Si aspetta solo un metodo :write(). Questo rende il tuo codice flessibile. Puoi aggiungere nuovi tipi di output in seguito senza modificare le funzioni esistenti.
Sfida
FacileCostruiamo un sistema di elaborazione dei pagamenti in cui si possano usare in modo intercambiabile metodi di pagamento diversi! Creerai due classi di pagamento completamente indipendenti che condividono un'interfaccia comune, un metodo :process(amount), permettendo a qualsiasi codice che gestisca i pagamenti di funzionare con entrambi i tipi senza distinguerli.
Organizzerai il codice in tre file:
CreditCard.lua: una classe di pagamento con un costruttore:new(cardNumber)che memorizza il numero della carta. Il suo metodo:process(amount)dovrebbe stampareCharging {amount} to card {cardNumber}.BankTransfer.lua: una classe di pagamento completamente separata con un costruttore:new(accountId). Il suo metodo:process(amount)dovrebbe stampareTransferring {amount} from account {accountId}.main.lua: crea una funzione chiamatahandlePayment(paymentMethod, amount)che accetta qualsiasi oggetto di pagamento e un importo, quindi chiama:process(amount)su di esso. Questa funzione non deve sapere se sta gestendo una carta di credito o un bonifico bancario. Si aspetta semplicemente che l'oggetto abbia un metodo:process().
Riceverai tre input:
- Un numero di carta di credito (ad es.,
4532-1234-5678) - Un ID di conto bancario (ad es.,
ACC-9876) - Un importo del pagamento (ad es.,
150)
Nel file principale, crea un'istanza di ciascun tipo di pagamento usando i primi due input. Poi usa la tua funzione handlePayment per elaborare l'importo indicato con entrambi i metodi di pagamento: prima la carta di credito, poi il bonifico bancario.
Ad esempio, se gli input sono 1111-2222-3333, ACC-5555 e 75, l'output dovrebbe essere:
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555Il bello di questa progettazione è che la tua funzione handlePayment funziona con qualsiasi oggetto che implementi :process(). Potresti aggiungere una classe PayPal domani e, purché abbia un metodo :process(amount), funzionerebbe con la funzione esistente senza alcuna modifica!
Provalo tu
-- Importa le classi di pagamento
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')
-- Leggi i dati di input
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())
-- TODO: Crea la funzione handlePayment che accetta qualsiasi oggetto di pagamento
-- e un importo, quindi chiama :process(amount) sull'oggetto
-- TODO: Crea istanze di CreditCard e BankTransfer usando i dati di input
-- TODO: Usa handlePayment per elaborare l'importo con entrambi i metodi di pagamento
-- Prima la carta di credito, poi il bonifico bancario
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Il concetto di «self»
Tabelle con funzioni«self» esplicitoLa sintassi dei due puntiPunto o due puntiRiepilogo - Spostare il punto4Progetto: Banca digitale
Configurazione del progettoMetodo di deposito7Polimorfismo e sovrascrittura
Sovrascrivere i metodiChiamare i metodi della classe genitoreDuck TypingInterfaccia comuneVerificare il tipoRiepilogo - Ruoli dei dipendenti2Schema del prototipo di classe
Il concetto di prototipoCollegare con __indexIl costruttore :new()Inizializzare gli attributiIstanze indipendentiRiepilogo - Fabbrica di auto5Sovraccarico degli operatori nella OOP
Sommare oggettiSottrarre oggettiConcatenare oggettiConfrontare oggetti (<, >)Ripasso - Calcoli con il portafoglio8Incapsulamento
Convenzioni di denominazioneClosure per la privacyAccesso tramite closureTabelle di sola letturaLogica di convalidaRiepilogo - Cassaforte sicura11Modelli di progettazione (base)
Funzioni factoryTabella singletonPattern IteratorObserver (listener)Riepilogo - Factory per logger3Stato e comportamento degli oggetti
Variabili di istanzaMetodi getterMetodi setterProprietà calcolateFormattazione delle stringheControlli di uguaglianzaRiepilogo - Voto dello studente6Fondamenti dell'ereditarietà
Configurare l'ereditarietàEreditare i metodiEstendere il costruttoreAggiungere metodi alla classe figliaCondiviso vs specificoRiepilogo - Gerarchia delle formeEsercitati da solo: Compilatore Lua online