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 lavorare insieme 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 ciascuna, 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 produrre 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] Avvio in corso
logMessage(screen, "Starting up") -- [SCREEN] Avvio in corsoLa funzione logMessage non sa né le interessa 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 diversi metodi di pagamento! Creerai due classi di pagamento completamente indipendenti che condividono un'interfaccia comune, un metodo :process(amount), consentendo a qualsiasi codice che gestisce 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 un 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 solo 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 di pagamento (ad es.,
150)
Nel file principale, crea un'istanza di ciascun tipo di pagamento usando i primi due input. Poi usa la 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 questo design è che la funzione handlePayment funziona con qualsiasi oggetto che implementi :process(). Domani potresti aggiungere una classe PayPal 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, poi 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 con la carta di credito, poi con 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