Gemeinsames Interface
Teil des Abschnitts Object Oriented Programming der Lua-Journey von Coddy — Lektion 40 von 70.
Duck-Typing hat uns gezeigt, dass nicht verwandte Klassen zusammenarbeiten können, wenn sie dieselben Methodennamen teilen. Diese Idee führt zu einem leistungsstarken Designkonzept: der gemeinsamen Schnittstelle. Wenn Sie absichtlich mehrere Klassen so entwerfen, dass sie dieselben Methodensignaturen haben, erstellen Sie austauschbare Komponenten.
Stellen Sie sich vor, Sie bauen ein Logging-System. Vielleicht möchten Sie Nachrichten an verschiedene Ziele senden – eine Datei, den Bildschirm oder sogar ein Netzwerk. Anstatt für jedes Ziel separaten Code zu schreiben, entwerfen Sie alle Ihre Ausgabe-Klassen so, dass sie dieselbe :write()-Methode implementieren:
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)
endJetzt kann jeder Code, der Nachrichten ausgeben muss, beide Typen akzeptieren:
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] Wird gestartet
logMessage(screen, "Starting up") -- [SCREEN] Wird gestartetDie Funktion logMessage weiß nicht und es ist ihr egal, welche Klasse sie erhält. Sie erwartet lediglich eine :write()-Methode. Dies macht Ihren Code flexibel – Sie können später neue Ausgabetypen hinzufügen, ohne bestehende Funktionen zu ändern.
Aufgabe
EinfachLassen Sie uns ein Zahlungsverarbeitungssystem erstellen, bei dem verschiedene Zahlungsmethoden austauschbar verwendet werden können! Sie werden zwei völlig voneinander unabhängige Zahlungsklassen erstellen, die eine gemeinsame Schnittstelle teilen – eine :process(amount)-Methode. Dies ermöglicht es jedem Code, der Zahlungen verarbeitet, mit beiden Typen zu arbeiten, ohne den Unterschied kennen zu müssen.
Sie werden Ihren Code in drei Dateien organisieren:
CreditCard.lua: Eine Zahlungsklasse mit einem:new(cardNumber)-Konstruktor, der die Kartennummer speichert. Ihre:process(amount)-Methode sollteCharging {amount} to card {cardNumber}ausgeben.BankTransfer.lua: Eine völlig separate Zahlungsklasse mit einem:new(accountId)-Konstruktor. Ihre:process(amount)-Methode sollteTransferring {amount} from account {accountId}ausgeben.main.lua: Erstellen Sie eine Funktion namenshandlePayment(paymentMethod, amount), die ein beliebiges Zahlungsobjekt und einen Betrag akzeptiert und dann:process(amount)darauf aufruft. Diese Funktion muss nicht wissen, ob sie es mit einer Kreditkarte oder einer Banküberweisung zu tun hat – sie erwartet lediglich, dass das Objekt über eine:process()-Methode verfügt.
Sie erhalten drei Eingaben:
- Eine Kreditkartennummer (z. B.
4532-1234-5678) - Eine Bankkonto-ID (z. B.
ACC-9876) - Ein Zahlungsbetrag (z. B.
150)
Erstellen Sie in Ihrer Hauptdatei eine Instanz jedes Zahlungstyps unter Verwendung der ersten beiden Eingaben. Verwenden Sie dann Ihre handlePayment-Funktion, um den angegebenen Betrag über beide Zahlungsmethoden zu verarbeiten – zuerst die Kreditkarte, dann die Banküberweisung.
Wenn die Eingaben beispielsweise 1111-2222-3333, ACC-5555 und 75 sind, sollte die Ausgabe wie folgt aussehen:
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555Das Schöne an diesem Design ist, dass Ihre handlePayment-Funktion mit jedem Objekt funktioniert, das :process() implementiert. Sie könnten morgen eine PayPal-Klasse hinzufügen, und solange sie eine :process(amount)-Methode hat, würde sie ohne Änderungen mit Ihrer bestehenden Funktion funktionieren!
Spickzettel
Eine gemeinsame Schnittstelle (Common Interface) ist ein Entwurfsmuster, bei dem mehrere nicht verwandte Klassen absichtlich dieselben Methodensignaturen implementieren, wodurch sie austauschbar werden.
Indem Sie Klassen so entwerfen, dass sie Methodennamen gemeinsam nutzen, können Sie Funktionen schreiben, die mit jedem Objekt funktionieren, das diese Schnittstelle implementiert:
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)
endFunktionen können jedes Objekt akzeptieren, das die erwartete Methode implementiert:
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 upDie Funktion logMessage muss nicht wissen, welche Klasse sie erhält – sie erwartet lediglich eine :write()-Methode. Dies macht den Code flexibel und ermöglicht das Hinzufügen neuer Typen, ohne bestehende Funktionen ändern zu müssen.
Probier es selbst
-- Importiere die Zahlungsklassen
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')
-- Eingaben lesen
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())
-- TODO: Erstelle die handlePayment-Funktion, die ein beliebiges Zahlungsobjekt akzeptiert
-- und einen Betrag, und dann :process(amount) darauf aufruft
-- TODO: Erstelle Instanzen von CreditCard und BankTransfer unter Verwendung der Eingaben
-- TODO: Verwende handlePayment, um den Betrag über beide Zahlungsmethoden zu verarbeiten
-- Zuerst die Kreditkarte, dann die Banküberweisung
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Object Oriented Programming
1Das 'self'-Konzept
Tabellen mit FunktionenExplizites 'self'Die Doppelpunkt-SyntaxPunkt vs. DoppelpunktZusammenfassung – Beweglicher Punkt4Projekt: Digitale Bank
Projekt-SetupEinzahlungsmethode7Polymorphie & Überschreiben
Methoden überschreibenElternmethoden aufrufenDuck TypingGemeinsames InterfaceTyp prüfenRückblick - Mitarbeiterrollen10Projekt: Formen-Manager
Projekt-SetupRechteck-Klasse2Klassen-Prototyp-Muster
Das Prototyp-KonzeptVerknüpfung mit __indexDer :new()-KonstruktorAttribute initialisierenUnabhängige InstanzenZusammenfassung – Autofabrik5Operatorüberladung in OOP
Objekte addierenObjekte subtrahierenObjekte verkettenObjekte vergleichen (<, >)Rückblick – Wallet-Mathematik3Objektzustand und -verhalten
InstanzvariablenGetter-MethodenSetter-MethodenBerechnete EigenschaftenStrings formatierenGleichheitsprüfungenZusammenfassung – Studentennote6Grundlagen der Vererbung
Das Vererbungs-SetupMethoden vererbenDen Konstruktor erweiternChild-Methoden hinzufügenGemeinsam vs. EinzigartigZusammenfassung - Formen-Hierarchie9Komposition & Mixins
Has-A-BeziehungDelegationEinfache MixinsAnwendung mehrerer MixinsMixins vs. VererbungZusammenfassung – Roboter-Montage12Abschluss-Herausforderungen
Wiederholung - InventarsystemWiederholung - BenutzerverwaltungWiederholung - Score-TrackerFinaler OOP-Check