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, erstellst du austauschbare Komponenten.
Stell dir vor, du baust 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 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, entweder einen der beiden 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] Startet
logMessage(screen, "Starting up") -- [SCREEN] StartetDie Funktion logMessage weiß nicht und kümmert sich nicht darum, welche Klasse sie erhält. Sie erwartet lediglich eine :write()-Methode. Dadurch wird dein Code flexibel. Du kannst später neue Ausgabetypen hinzufügen, ohne bestehende Funktionen zu ändern.
Aufgabe
EinfachWir erstellen ein Zahlungsabwicklungssystem, in dem verschiedene Zahlungsmethoden austauschbar verwendet werden können! Du erstellst zwei völlig unabhängige Zahlungsklassen, die eine gemeinsame Schnittstelle, eine :process(amount)-Methode, verwenden. Dadurch kann jeder Code, der Zahlungen verarbeitet, mit beiden Typen arbeiten, ohne den Unterschied zu kennen.
Du organisierst deinen Code über drei Dateien:
CreditCard.lua: Eine Zahlungsklasse mit einem:new(cardNumber)-Konstruktor, der die Kartennummer speichert. Ihre:process(amount)-Methode sollCharging {amount} to card {cardNumber}ausgeben.BankTransfer.lua: Eine vollständig separate Zahlungsklasse mit einem:new(accountId)-Konstruktor. Ihre:process(amount)-Methode sollTransferring {amount} from account {accountId}ausgeben.main.lua: Erstelle eine Funktion namenshandlePayment(paymentMethod, amount), die ein beliebiges Zahlungsobjekt und einen Betrag akzeptiert und anschließend:process(amount)für dieses Objekt aufruft. Diese Funktion muss nicht wissen, ob sie mit einer Kreditkarte oder einer Banküberweisung arbeitet. Sie erwartet lediglich, dass das Objekt eine:process()-Methode besitzt.
Du erhältst drei Eingaben:
- Eine Kreditkartennummer (z. B.
4532-1234-5678) - Eine Bankkonto-ID (z. B.
ACC-9876) - Einen Zahlungsbetrag (z. B.
150)
Erstelle in deiner Hauptdatei jeweils eine Instanz jedes Zahlungstyps unter Verwendung der ersten beiden Eingaben. Verwende dann deine handlePayment-Funktion, um den angegebenen Betrag über beide Zahlungsmethoden zu verarbeiten: zuerst über die Kreditkarte, dann über die Banküberweisung.
Wenn die Eingaben beispielsweise 1111-2222-3333, ACC-5555 und 75 lauten, sollte die Ausgabe folgendermaßen aussehen:
Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555Das Schöne an diesem Entwurf ist, dass deine handlePayment-Funktion mit jedem Objekt funktioniert, das :process() implementiert. Du könntest morgen eine PayPal-Klasse hinzufügen, und solange sie eine :process(amount)-Methode besitzt, würde sie ohne Änderungen mit deiner bestehenden Funktion funktionieren!
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 mit den 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 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