Gemeinsames Interface
Teil des Abschnitts Objektorientierte Programmierung der Lua-Journey von Coddy. Lektion 40 von 70.
Duck-Typing hat uns gezeigt, dass nicht verwandte Klassen zusammenarbeiten können, wenn sie Methodennamen gemeinsam haben. Diese Idee führt zu einem leistungsstarken Designkonzept: der gemeinsamen Schnittstelle. Wenn du mehrere Klassen bewusst so entwirfst, dass sie dieselben Methodensignaturen haben, schaffst du austauschbare Komponenten.
Stell dir vor, du entwickelst ein Protokollierungssystem. Vielleicht möchtest du Nachrichten an verschiedene Ziele senden: eine Datei, den Bildschirm oder sogar ein Netzwerk. Anstatt für jedes Ziel separaten Code zu schreiben, entwirfst du alle deine Ausgabeklassen 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ß weder, welche Klasse sie erhält, noch interessiert sie sich dafür. Sie erwartet lediglich eine :write()-Methode. Dadurch wird dein Code flexibel. Du kannst später neue Ausgabetypen hinzufügen, ohne vorhandene Funktionen zu ändern.
Aufgabe
EinfachLass uns ein Zahlungsabwicklungssystem entwickeln, in dem verschiedene Zahlungsmethoden austauschbar verwendet werden können! Du erstellst zwei völlig unabhängige Zahlungsklassen mit einer gemeinsamen Schnittstelle, der Methode :process(amount). Dadurch kann jeder Code, der Zahlungen verarbeitet, mit beiden Typen arbeiten, ohne den Unterschied zu kennen.
Du verteilst deinen Code auf drei Dateien:
CreditCard.lua: Eine Zahlungsklasse mit einem Konstruktor:new(cardNumber), der die Kartennummer speichert. Ihre Methode:process(amount)sollCharging {amount} to card {cardNumber}ausgeben.BankTransfer.lua: Eine völlig unabhängige Zahlungsklasse mit einem Konstruktor:new(accountId). Ihre Methode:process(amount)sollTransferring {amount} from account {accountId}ausgeben.main.lua: Erstelle eine Funktion namenshandlePayment(paymentMethod, amount), die ein beliebiges Zahlungsobjekt und einen Betrag entgegennimmt und dann darauf:process(amount)aufruft. Diese Funktion muss nicht wissen, ob sie eine Kreditkarte oder eine Banküberweisung verarbeitet. Sie erwartet lediglich, dass das Objekt eine Methode:process()hat.
Du erhältst drei Eingaben:
- Eine Kreditkartennummer (z. B.
4532-1234-5678) - Eine Bankkontonummer (z. B.
ACC-9876) - Einen Zahlungsbetrag (z. B.
150)
Erstelle in deiner Hauptdatei mit den ersten beiden Eingaben jeweils eine Instanz der beiden Zahlungsarten. Verwende dann deine Funktion handlePayment, um den angegebenen Betrag mit beiden Zahlungsmethoden zu verarbeiten: zuerst mit der Kreditkarte, dann mit der Banküberweisung.
Wenn die Eingaben beispielsweise 1111-2222-3333, ACC-5555 und 75 lauten, sollte die Ausgabe wie folgt aussehen:
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555Das Besondere an diesem Entwurf ist, dass deine Funktion handlePayment mit jedem Objekt funktioniert, das :process() implementiert. Du könntest morgen eine Klasse PayPal hinzufügen. Solange sie eine Methode :process(amount) hat, würde sie mit deiner bestehenden Funktion ohne Änderungen funktionieren!
Probier es selbst
-- Importiere die Zahlungsklassen
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')
-- Lies die Eingaben ein
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())
-- TODO: Erstelle die Funktion handlePayment, die ein beliebiges Zahlungsobjekt akzeptiert
-- und einen Betrag entgegennimmt und dann darauf :process(amount) aufruft
-- TODO: Erstelle mithilfe der Eingaben Instanzen von CreditCard und BankTransfer
-- TODO: Verwende handlePayment, um den Betrag mit beiden Zahlungsmethoden zu verarbeiten
-- Zuerst mit der Kreditkarte, dann per Banküberweisung
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
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-KlasseKreis-KlasseUmfang-MethodeFormen-SammlungGesamtflächeFormen filtern2Klassen-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Übe selbstständig: Online-Lua-Compiler