Closures pour la confidentialité
Fait partie de la section Object Oriented Programming du Journey Lua de Coddy — leçon 44 sur 70.
Les conventions de nommage signalent l'intention, mais elles n'empêchent pas réellement l'accès. N'importe qui peut toujours écrire player._health = 999 et cela fonctionne. Pour une véritable confidentialité, Lua propose une approche différente : stocker les données dans des variables locales à l'intérieur du constructeur au lieu de self.
L'idée clé est que les variables locales à l'intérieur d'une fonction ne sont accessibles qu'au sein de cette fonction — et de toutes les fonctions définies à l'intérieur de celle-ci. C'est ce qu'on appelle une fermeture (closure) :
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Vraiment privé ! Pas sur self.
local obj = {}
setmetatable(obj, Counter)
return obj
endDans cet exemple, count n'existe qu'à l'intérieur de la fonction :new(). Il n'est pas stocké dans obj, il n'y a donc aucun moyen d'y accéder de l'extérieur — myCounter.count renvoie nil, et il n'y a pas de champ préfixé par un souligné à contourner.
La variable count réside dans la portée de la closure. Elle persiste tant que l'objet existe, mais elle est complètement cachée du code externe. Contrairement à self._count, qui n'est qu'une simple convention, cette approche rend les données véritablement inaccessibles.
Bien sûr, les données cachées ne sont pas utiles si vous ne pouvez jamais interagir avec elles. Dans la leçon suivante, vous apprendrez à définir des méthodes à l'intérieur du constructeur capables de lire et de modifier ces variables privées, vous offrant ainsi un accès contrôlé tout en préservant une véritable encapsulation.
Défi
FacileConstruisons une classe SecretKeeper qui démontre une véritable confidentialité des données en utilisant les fermetures (closures) ! Contrairement à la convention de l'underscore de la leçon précédente, cette approche rend les données véritablement inaccessibles depuis l'extérieur de l'objet.
Vous organiserez votre code sur deux fichiers :
SecretKeeper.lua: Créez une classe où le secret est stocké dans une variable locale à l'intérieur du constructeur — et non surself. Le constructeur:new(secretValue)doit stocker le secret dans une variable locale qui n'existe qu'au sein de la fermeture. Pour l'instant, l'objet n'aura aucun moyen d'accéder à ce secret (cela viendra dans la prochaine leçon !), mais vous devez également stocker un champ publicnamedéfini sur"Keeper"sur l'instance afin que nous puissions vérifier que l'objet fonctionne.main.lua: Importez votre module SecretKeeper et lisez une valeur secrète à partir de l'entrée. Créez une instance de SecretKeeper avec ce secret. Démontrez ensuite que le secret est véritablement privé en affichant trois choses :- Le champ
namedu keeper (doit afficherKeeper) - Ce qui se passe lorsque vous essayez d'accéder à
keeper.secret(doit êtrenil) - Ce qui se passe lorsque vous essayez d'accéder à
keeper._secret(doit également êtrenil)
- Le champ
Vous recevrez une entrée :
- La valeur secrète à stocker (par exemple,
TopSecret123)
Votre sortie doit comporter trois lignes montrant que, bien que le champ public fonctionne, le secret est complètement caché :
Name: Keeper
secret: nil
_secret: nilLa sortie sera la même quel que soit le secret que vous passez — car il n'y a aucun moyen d'y accéder ! La variable locale à l'intérieur du constructeur est invisible pour le monde extérieur. Cela prouve que la confidentialité basée sur les fermetures est fondamentalement différente de la convention de nommage par underscore.
Par exemple, si l'entrée est MyPassword, la sortie doit toujours être :
Name: Keeper
secret: nil
_secret: nilAide-mémoire
Pour créer des données véritablement privées en Lua, stockez-les dans des variables locales à l'intérieur du constructeur au lieu de les mettre sur self. Cela utilise une fermeture (closure) — les variables locales ne sont accessibles qu'à l'intérieur de la fonction où elles sont définies et de toutes les fonctions définies à l'intérieur de celle-ci.
Exemple d'une classe avec des données privées :
local Counter = {}
Counter.__index = Counter
function Counter:new()
local count = 0 -- Véritablement privé ! Pas sur self.
local obj = {}
setmetatable(obj, Counter)
return obj
endDans cet exemple, count n'existe qu'à l'intérieur de la fonction :new(). Il n'est pas stocké dans obj, donc myCounter.count renvoie nil. Il n'y a aucun moyen d'y accéder de l'extérieur — contrairement à self._count (qui n'est qu'une convention de nommage), cette approche rend les données véritablement inaccessibles.
La variable persiste tant que l'objet existe mais reste complètement cachée du code externe, offrant une véritable encapsulation.
Essayez vous-même
-- Charger le module SecretKeeper
local SecretKeeper = require('SecretKeeper')
-- Lire la valeur secrète depuis l'entrée
local secretValue = io.read()
-- TODO : Créer une instance de SecretKeeper avec la valeur secrète
-- TODO : Afficher le champ name du keeper (format : "Name: <name>")
-- TODO : Afficher ce qui se passe lors de l'accès à keeper.secret (format : "secret: <value>")
-- TODO : Afficher ce qui se passe lors de l'accès à keeper._secret (format : "_secret: <value>")
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