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)
endDé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émarrageLa 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.
Défi
FacileConstruisons 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 afficherCharging {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 afficherTransferring {amount} from account {accountId}.main.lua: Créez une fonction nomméehandlePayment(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 :
- Un numéro de carte de crédit (par ex.,
4532-1234-5678) - Un identifiant de compte bancaire (par ex.,
ACC-9876) - 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-5555La 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)
endLes 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 upLa 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
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
1Le concept de « self »
Tables avec fonctions« self » expliciteLa syntaxe avec deux-pointsPoint vs deux-pointsRécapitulatif - Point mobile4Projet : Banque numérique
Configuration du projetMéthode de dépôt7Polymorphisme et Redéfinition
Redéfinition de méthodesAppeler des méthodes parentesDuck TypingInterface communeVérification du typeRécapitulatif - Rôles des employés10Projet : Gestionnaire de formes
Configuration du projetClasse Rectangle2Modèle de prototype de classe
Le concept de prototypeLiaison avec __indexLe constructeur :new()Initialisation des attributsInstances indépendantesRécapitulatif - Usine de voitures5Surcharge d'opérateurs en POO
Additionner des objetsSoustraire des objetsConcaténer des objetsComparer des objets (<, >)Récapitulatif - Calculs de portefeuille8Encapsulation
Conventions de nommageClosures pour la confidentialitéAccès via les ClosuresTables en lecture seuleLogique de validationRécapitulatif - Coffre-fort sécurisé11Patrons de conception (Lite)
Fonctions FactoryTable SingletonPatron IteratorObserver (Écouteur)Récapitulatif - Logger Factory3État et comportement des objets
Variables d'instanceMéthodes GetterMéthodes SetterPropriétés calculéesFormatage de chaînesVérifications d'égalitéRécapitulatif - Note de l'étudiant6Bases de l'héritage
Mise en place de l'héritageHériter des méthodesÉtendre le constructeurAjouter des méthodes enfantsPartagé vs UniqueRécapitulatif - Hiérarchie des formes