Menu
Coddy logo textTech

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)
end

Jetzt 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 gestartet

Die 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.

challenge icon

Aufgabe

Einfach

Lassen 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 sollte Charging {amount} to card {cardNumber} ausgeben.
  • BankTransfer.lua: Eine völlig separate Zahlungsklasse mit einem :new(accountId)-Konstruktor. Ihre :process(amount)-Methode sollte Transferring {amount} from account {accountId} ausgeben.
  • main.lua: Erstellen Sie eine Funktion namens handlePayment(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:

  1. Eine Kreditkartennummer (z. B. 4532-1234-5678)
  2. Eine Bankkonto-ID (z. B. ACC-9876)
  3. 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-5555

Das 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)
end

Funktionen 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 up

Die 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
quiz iconTeste dich selbst

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