Menu
Coddy logo textTech

Interface commune

Fait partie de la section Object Oriented Programming du Journey Lua de Coddy — leçon 40 sur 70.

Le duck typing nous a montré que des classes non liées peuvent travailler ensemble si elles partagent des noms de méthodes. Cette idée mène à un concept de conception puissant : l'interface commune. Lorsque vous concevez intentionnellement plusieurs classes pour qu'elles aient les mêmes signatures de méthode, vous créez des composants interchangeables.

Imaginez que vous construisez un système de journalisation. Vous pourriez vouloir envoyer des messages vers différentes destinations : un fichier, l'écran ou même un réseau. Au lieu d'écrire du code séparé pour chacun, vous concevez toutes vos classes de sortie pour implémenter la même méthode :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)
end

Désormais, tout code devant afficher des messages peut accepter l'un ou l'autre type :

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] Démarrage
logMessage(screen, "Starting up")  -- [SCREEN] Démarrage

La fonction logMessage ne sait pas et ne se soucie pas de la classe qu'elle reçoit. Elle attend seulement une méthode :write(). Cela rend votre code flexible — vous pouvez ajouter de nouveaux types de sortie plus tard sans modifier les fonctions existantes.

challenge icon

Défi

Facile

Construisons un système de traitement des paiements où différentes méthodes de paiement peuvent être utilisées de manière interchangeable ! Vous allez créer deux classes de paiement complètement distinctes qui partagent une interface commune — une méthode :process(amount) — permettant à tout code gérant les paiements de fonctionner avec l'un ou l'autre type sans connaître la différence.

Vous organiserez votre code sur trois fichiers :

  • CreditCard.lua : Une classe de paiement avec un constructeur :new(cardNumber) qui stocke le numéro de carte. Sa méthode :process(amount) doit afficher Charging {amount} to card {cardNumber}.
  • BankTransfer.lua : Une classe de paiement complètement séparée avec un constructeur :new(accountId). Sa méthode :process(amount) doit afficher Transferring {amount} from account {accountId}.
  • main.lua : Créez une fonction nommée handlePayment(paymentMethod, amount) qui accepte n'importe quel objet de paiement et un montant, puis appelle :process(amount) sur celui-ci. Cette fonction n'a pas besoin de savoir s'il s'agit d'une carte de crédit ou d'un virement bancaire — elle s'attend simplement à ce que l'objet possède une méthode :process().

Vous recevrez trois entrées :

  1. Un numéro de carte de crédit (par ex., 4532-1234-5678)
  2. Un identifiant de compte bancaire (par ex., ACC-9876)
  3. Un montant de paiement (par ex., 150)

Dans votre fichier principal, créez une instance de chaque type de paiement en utilisant les deux premières entrées. Utilisez ensuite votre fonction handlePayment pour traiter le montant donné via les deux méthodes de paiement — d'abord la carte de crédit, puis le virement bancaire.

Par exemple, si les entrées sont 1111-2222-3333, ACC-5555, et 75, la sortie devrait être :

Charging 75 to card 1111-2222-3333
Transferring 75 from account ACC-5555

La beauté de cette conception est que votre fonction handlePayment fonctionne avec n'importe quel objet qui implémente :process(). Vous pourriez ajouter une classe PayPal demain, et tant qu'elle possède une méthode :process(amount), elle fonctionnerait avec votre fonction existante sans aucune modification !

Aide-mémoire

Une interface commune est un patron de conception où plusieurs classes non liées implémentent intentionnellement les mêmes signatures de méthode, ce qui les rend interchangeables.

En concevant des classes qui partagent des noms de méthodes, vous pouvez écrire des fonctions qui fonctionnent avec n'importe quel objet implémentant cette interface :

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

Les fonctions peuvent accepter n'importe quel objet qui implémente la méthode attendue :

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

La fonction logMessage n'a pas besoin de savoir quelle classe elle reçoit — elle attend seulement une méthode :write(). Cela rend le code flexible et permet d'ajouter de nouveaux types sans modifier les fonctions existantes.

Essayez vous-même

-- Importer les classes de paiement
local CreditCard = require('CreditCard')
local BankTransfer = require('BankTransfer')

-- Lire les entrées
local cardNumber = io.read()
local accountId = io.read()
local amount = tonumber(io.read())

-- TODO: Créer la fonction handlePayment qui accepte n'importe quel objet de paiement
-- et un montant, puis appelle :process(amount) sur celui-ci


-- TODO: Créer des instances de CreditCard et BankTransfer en utilisant les entrées


-- TODO: Utiliser handlePayment pour traiter le montant via les deux méthodes de paiement
-- D'abord la carte de crédit, puis le virement bancaire
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Object Oriented Programming