Duck Typing
Fait partie de la section Object Oriented Programming du Journey Lua de Coddy — leçon 39 sur 70.
Lua ne se soucie pas de la classe d'un objet — il se soucie uniquement de ce que l'objet peut faire. Ce concept est appelé le duck typing : « Si ça marche comme un canard et que ça cancane comme un canard, alors c'est un canard. » En termes de programmation, si un objet possède la méthode que vous appelez, cela fonctionne.
Cela signifie que des classes totalement indépendantes peuvent être utilisées ensemble, tant qu'elles partagent le même nom de méthode :
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endCes classes n'ont aucune relation d'héritage — elles sont complètement indépendantes. Pourtant, nous pouvons les traiter de manière identique :
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- Sortie :
-- Dessin d'un cercle
-- Dessin d'un carré
-- Dessin d'un cercleLa boucle ne vérifie pas le type de chaque objet. Elle appelle simplement :draw() et part du principe que la méthode existe. Cette flexibilité est puissante — vous pouvez ajouter de nouveaux types de formes sans modifier le code de la boucle du tout. Tant que la nouvelle classe implémente :draw(), cela fonctionne tout simplement.
Défi
FacileDémontrons la puissance du duck typing en construisant un système de notification avec des classes complètement indépendantes qui peuvent toutes être traitées de la même manière !
Vous allez créer trois classes de notification indépendantes — aucune d'entre elles n'hérite de l'autre — mais elles implémentent toutes une méthode :send(). Cela signifie que vous pouvez parcourir une collection mixte et appeler :send() sur chacune d'elles sans vous soucier de son type réel.
Vous organiserez votre code sur quatre fichiers :
Email.lua: Une classe avec un constructeur:new(recipient)qui stocke le destinataire de l'e-mail. Sa méthode:send()doit afficherEmailing {recipient}.SMS.lua: Une classe complètement séparée avec un constructeur:new(phoneNumber). Sa méthode:send()doit afficherTexting {phoneNumber}.PushNotification.lua: Une autre classe indépendante avec un constructeur:new(deviceId). Sa méthode:send()doit afficherPushing to {deviceId}.main.lua: Rassemblez tout ! Lisez trois entrées, créez une instance de chaque type de notification, placez-les toutes dans une seule table, puis bouclez et appelez:send()sur chacune d'elles.
Vous recevrez trois entrées :
- Un destinataire d'e-mail (par exemple,
alice@example.com) - Un numéro de téléphone (par exemple,
555-1234) - Un identifiant d'appareil (par exemple,
device_99)
Dans votre fichier principal, créez les trois objets de notification, stockez-les dans une table et parcourez la table en appelant :send() sur chacun. L'ordre doit être : Email en premier, puis SMS, puis PushNotification.
Par exemple, si les entrées sont bob@mail.com, 555-9876, et phone_42, la sortie devrait être :
Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42Remarquez comment votre boucle n'a pas besoin de vérifier le type de chaque objet — elle fait simplement confiance au fait que chaque objet de la collection possède une méthode :send(). C'est le duck typing en action ! S'il peut :send(), c'est une notification.
Aide-mémoire
Lua utilise le duck typing (typage canard) : si un objet possède la méthode que vous appelez, cela fonctionne, quelle que soit sa classe. Le principe est : « si cela marche comme un canard et cancane comme un canard, alors c'est un canard. »
Des classes totalement indépendantes peuvent être utilisées de manière interchangeable tant qu'elles partagent le même nom de méthode :
local Circle = {}
Circle.__index = Circle
function Circle:new()
local obj = {}
setmetatable(obj, Circle)
return obj
end
function Circle:draw()
print("Drawing a circle")
end
local Square = {}
Square.__index = Square
function Square:new()
local obj = {}
setmetatable(obj, Square)
return obj
end
function Square:draw()
print("Drawing a square")
endCes classes n'ont aucune relation d'héritage, pourtant elles peuvent être traitées de manière identique :
local shapes = {Circle:new(), Square:new(), Circle:new()}
for _, shape in ipairs(shapes) do
shape:draw()
end
-- Output:
-- Drawing a circle
-- Drawing a square
-- Drawing a circleLa boucle ne vérifie pas les types d'objets — elle appelle simplement la méthode et part du principe qu'elle existe. Vous pouvez ajouter de nouveaux types sans modifier le code existant, tant qu'ils implémentent la méthode requise.
Essayez vous-même
-- main.lua
-- Rassembler tous les types de notification et démontrer le duck typing
-- Importer les classes de notification
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')
-- Lire les entrées
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()
-- TODO : Créer une instance de chaque type de notification
-- TODO : Mettre toutes les notifications dans une seule table (Email d'abord, puis SMS, puis PushNotification)
-- TODO : Parcourir la table et appeler :send() sur chaque notification
-- Cela démontre le duck typing - nous ne vérifions pas le type, nous appelons simplement :send()
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