Orienté objet avec Lua
La programmation orientée objet (POO) est une architecture de programmation informatique très populaire qui conçoit des applications en créant et manipulant des objets.
Les langages de programmation suivants prennent tous en charge la programmation orientée objet :
- C++
- Java
- Objective-C
- Smalltalk
- C#
- Ruby
Lua est un langage de script léger. Bien qu'il ne dispose pas de fonctionnalités orientées objet (OO) puissantes intégrées comme Java ou C++, il est très flexible et peut mettre en œuvre la programmation orientée objet grâce à certaines techniques.
Fonctionnalités orientées objet
Encapsulation: Lier les données et les méthodes ensemble, masquer les détails d'implémentation, n'exposer que les interfaces nécessaires, et améliorer la sécurité et la maintenabilité.
Héritage: Réutiliser et étendre le code existant en dérivant de nouvelles classes, réduire le codage répétitif, et améliorer l'efficacité du développement et l'extensibilité.
Polymorphisme: La même opération se comporte différemment sur différents objets, prend en charge des appels d'interface unifiés, et renforce la flexibilité et l'extensibilité.
Abstraction: Simplifier des problèmes complexes en définissant des classes et interfaces de base, en masquant les détails inutiles pour faciliter la gestion de la complexité.
Orienté objet en Lua
Nous savons que les objets sont constitués de propriétés et de méthodes.
Les classes en Lua peuvent êtretable + functionsimulées.
En ce qui concerne l'héritage, il peut être simulé via des métatables (non recommandé ; simuler uniquement les objets les plus élémentaires suffit pour la plupart des implémentations).
En Lua, la structure la plus élémentaire est la table ; nous pouvons utiliser des tables pour créer des objets.
ClassName = {} -- 创建一个表作为类
Créer des objets via la méthode new (ou d'autres noms) et initialiser les propriétés de l'objet.
function ClassName:new(...)
local obj = {} -- 创建一个新的空表作为对象
setmetatable(obj, self) -- 设置元表,使对象继承类的方法
self.__index = self -- 设置索引元方法
-- 初始化对象的属性
obj:init(...) -- 可选:调用初始化函数
return obj
end
Une table est le type de données composite le plus élémentaire en Lua et peut être utilisée pour représenter les propriétés d'un objet.
La fonction en Lua peut être utilisée pour représenter des méthodes :
function ClassName:sayHello()
print("Hello, my name is " .. self.name)
end
Utiliser la méthode new pour créer des objets, et appeler les méthodes de la classe via l'objet.
local obj = ClassName:new("Alice") -- 创建对象
obj:sayHello() -- 调用对象的方法
En Lua, une table peut être considérée comme une variante d'un objet. Comme les objets, les tables ont un état (variables membres) et peuvent représenter des entités indépendantes.
Les tables n'ont pas seulement des membres de données, mais peuvent aussi contenir des fonctions membres similaires aux méthodes d'objet :
Exemple
Person = {name = "", age = 0}
-- Constructeur de Person
function Person:new(name, age)
local obj = {} -- Créer une nouvelle table comme objet
setmetatable(obj, self) -- Définir la métatable pour en faire une instance de Person
self.__index = self -- Définir la métaméthode d'index pour pointer vers Person
obj.name = name
obj.age = age
return obj
end
-- Ajouter une méthode : afficher les informations personnelles
function Person:introduce()
print("Je m'appelle " .. self.name .. " et j'ai " .. self.age .. " ans.")
end
Explication du code :
- Personest une table avec deux propriétés : name et age ; ces deux propriétés sont les propriétés par défaut de la classe.
- Person:new(name, age)est un constructeur utilisé pour créer de nouveaux objets Person.
- local obj = {}Créer une nouvelle table en tant qu'objet ; setmetatable(obj, self) définit la métatable pour que la table devienne une instance de la classe Person.
- self.__index = selfDéfinir la métaméthode index afin que obj puisse accéder aux propriétés et méthodes de la classe Person.
- introduceest une méthode de la classe Person qui affiche le nom et l'âge de l'objet Person.
Appel des méthodes :
-- 创建一个 Person 对象
local person1 = Person:new("Alice", 30)
-- 调用对象的方法
person1:introduce() -- 输出 "My name is Alice and I am 30 years old."
Un exemple simple
La classe simple suivante contient trois propriétés : area, length et breadth. La méthode printArea est utilisée pour afficher le résultat du calcul :
Exemple
Rectangle = {area = 0, length = 0, breadth = 0}
-- Constructeur pour créer des objets Rectangle
function Rectangle:new(o, length, breadth)
o = o or {} -- Si aucun objet n'est passé, créer une nouvelle table vide
setmetatable(o, self) -- Définir la métatable afin qu'elle hérite des méthodes de Rectangle
self.__index = self -- S'assurer que les méthodes et propriétés peuvent être trouvées lorsqu'on y accède
o.length = length or 0 -- Définir length, valeur par défaut à 0
o.breadth = breadth or 0 -- Définir breadth, par défaut à 0
o.area = o.length * o.breadth -- Calculer l'aire
return o
end
-- Afficher l'aire du rectangle
function Rectangle:printArea()
print("L'aire du rectangle est ", self.area)
end
Création d'objets
La création d'un objet est le processus d'allocation de mémoire pour une instance d'une classe; chaque classe a sa propre mémoire et partage des données publiques:
r = Rectangle:new(nil,10,20)
Accès aux propriétés
Nous pouvons utiliser le point.pour accéder aux propriétés de la classe:
print(r.length)
Accès aux fonctions membres
Nous pouvons utiliser le deux-points:pour accéder aux fonctions membres de la classe:
r:printArea()
La mémoire est allouée lorsque les objets sont initialisés.
Exemple complet
Ci-dessous, nous démontrons un exemple complet de programmation orientée objet en Lua:
Exemple
Rectangle = {area = 0, length = 0, breadth = 0}
-- Constructeur pour créer des objets Rectangle
function Rectangle:new(o, length, breadth)
o = o or {} -- Si aucun objet n'est passé, créer une nouvelle table vide
setmetatable(o, self) -- Définir la métatable afin qu'elle hérite des méthodes de Rectangle
self.__index = self -- S'assurer que les méthodes et propriétés peuvent être trouvées lorsqu'on y accède
o.length = length or 0 -- Définir length, valeur par défaut à 0
o.breadth = breadth or 0 -- Définir breadth, par défaut à 0
o.area = o.length * o.breadth -- Calculer l'aire
return o
end
-- Afficher l'aire du rectangle
function Rectangle:printArea()
print("L'aire du rectangle est ", self.area)
end
-- Exécuter l'exemple:
local rect1 = Rectangle:new(nil, 5, 10) -- Créer un rectangle de longueur 5 et de largeur 10
rect1:printArea() -- Sortie "L'aire du rectangle est 50"
local rect2 = Rectangle:new(nil, 7, 3) -- Créer un rectangle de longueur 7 et de largeur 3
rect2:printArea() -- Sortie "L'aire du rectangle est 21"
L'exécution du programme ci-dessus produit la sortie suivante :
矩形面积为 50 矩形面积为 21
Héritage en Lua
L'héritage signifie qu'un objet utilise directement les propriétés et méthodes d'un autre objet, et peut être utilisé pour étendre les propriétés et méthodes d'une classe de base.
L'héritage en Lua est implémenté en définissant la métatable de la sous-classe.
Nous pouvons créer une nouvelle table et définir sa métatable sur la classe parente.
Dans l'exemple suivant, la classe Square héritera des propriétés et méthodes de la classe Rectangle et effectuera des modifications sur cette base.
Rectangle = {area = 0, length = 0, breadth = 0}
-- Constructeur pour créer des objets Rectangle
function Rectangle:new(o, length, breadth)
o = o or {} -- Si aucun objet n'est passé, créer une nouvelle table vide
setmetatable(o, self) -- Définir la métatable afin qu'elle hérite des méthodes de Rectangle
self.__index = self -- S'assurer que les méthodes et propriétés peuvent être trouvées lorsqu'on y accède
o.length = length or 0 -- Définir length, valeur par défaut à 0
o.breadth = breadth or 0 -- Définir breadth, par défaut à 0
o.area = o.length * o.breadth -- Calculer l'aire
return o
end
-- Afficher l'aire du rectangle
function Rectangle:printArea()
print("L'aire du rectangle est ", self.area)
end
-- Définir la classe Square, héritant de la classe Rectangle
Square = Rectangle:new() -- Square hérite de la classe Rectangle
-- Redéfinir le constructeur (tous les côtés d'un carré sont égaux)
function Square:new(o, side)
o = o or {} -- Si aucun objet n'est passé, créer une nouvelle table vide
setmetatable(o, self) -- Définir la métatable afin qu'elle hérite des méthodes de Rectangle
self.__index = self -- S'assurer que les méthodes et propriétés peuvent être trouvées lorsqu'on y accède
o.length = side or 0 -- Définir la longueur du côté
o.breadth = side or 0 -- La largeur et la longueur du carré sont égales
o.area = o.length * o.breadth -- Calculer l'aire
return o
end
-- Exécuter l'exemple:
local rect = Rectangle:new(nil, 5, 10) -- Créer un rectangle de longueur 5 et de largeur 10
rect:printArea() -- Affiche "L'aire du rectangle est 50"
local square = Square:new(nil, 4) -- Créer un carré avec une longueur de côté de 4
square:printArea() -- Affiche "L'aire du rectangle est 16"
RectangleClasse: est toujours la classe Rectangle de base, ayantlength、breadthetareapropriétés, ainsi que des méthodes pour calculer et afficher l'aire.
SquareLa classe hérite deRectangle:SquareLa classe utiliseRectangle:new()pour hériterRectangleles méthodes et les propriétés de la classe. Comme la longueur et la largeur d'un carré sont égales, nousSquare:newnous avons redéfini le constructeur dans la méthode, en définissantlengthetbreadthdéfini à la même valeur (c'est-à-direside)。
Redéfinition du constructeur:Square:new(o, side)Lorsque la méthode crée un objet Square, elle utilise la longueur du côté transmisesidepour initialiserlengthetbreadthles propriétés, et calcule l'aire.
Résultat de l'exécution:
矩形面积为 50 矩形面积为 16
Redéfinition de fonction
En Lua, la redéfinition de fonction (aussi appelée redéfinition de méthode) désigne la redéfinition ou le remplacement d'une méthode existante de la classe parente par la sous-classe lors de l'héritage.
Une sous-classe peut modifier ou étendre le comportement des méthodes de la classe parente selon les besoins.
Dans l'exemple ci-dessus, la classe Square redéfinit le constructeur de la classe Rectangle, modifiant ainsi la façon dont les objets sont initialisés, en particulier en définissant la longueur et la largeur du rectangle sur la même valeur, car la caractéristique d'un carré est d'avoir des côtés de longueur égale.
Ensuite, nous utilisons une classe Animal et une classe Dog qui en hérite pour démontrer comment redéfinir des méthodes.
Animal = {name = "Inconnu"}
-- Constructeur de la classe Animal
function Animal:new(o, name)
o = o or {} -- Si aucun objet n'est passé, créer une nouvelle table vide
setmetatable(o, self) -- Définit la métatable pour qu'elle hérite des méthodes d'Animal
self.__index = self -- Permet à l'objet d'accéder aux méthodes d'Animal
o.name = name or "Inconnu" -- Définit le nom, la valeur par défaut est "Inconnu"
return o
end
-- Méthode de la classe Animal : émettre un son
function Animal:speak()
print(self.name .. " émet un son.")
end
-- Définit la classe Dog, héritant d'Animal
Dog = Animal:new() -- Dog hérite de la classe Animal
-- Redéfinit le constructeur de la classe Dog
function Dog:new(o, name, breed)
o = o or {} -- Si aucun objet n'est passé, créer une nouvelle table vide
setmetatable(o, self) -- Définir la métatable afin qu'elle hérite des méthodes de Dog et Animal
self.__index = self -- Permettre à l'objet d'accéder aux méthodes de Dog
o.name = name or "Inconnu"
o.breed = breed or "Inconnu"
return o
end
-- Redéfinir la méthode sound de la classe Dog (redéfinition de la méthode speak d'Animal)
function Dog:speak()
print(self.name .. " aboie.")
end
-- Créer un objet Animal
local animal = Animal:new(nil, "Animal générique")
animal:speak() -- Affiche "Animal générique émet un son."
-- Créer un objet Dog
local dog = Dog:new(nil, "Buddy", "Golden Retriever")
dog:speak() -- Sortie "Buddy barks."
AnimalClass: définit une classe de baseAnimal, qui anamepropriétés etspeakméthodes traditionnelles.speakLa méthode est une implémentation par défaut qui produit "un animal fait un bruit."DogHéritage de classeAnimal:DogLa classe hérite deAnimal, et via laDog:new()méthode crée ses propres instances.Redéfinition
speakméthode: dans laDogclasse, a redéfini laspeakméthode, en changeant son comportement du « émettre un son » de la classe parente à « aboiement de chien ». C'est une manifestation de la redéfinition de méthode. La sous-classe (Dog) a modifié la classe parente (Animal) le comportement de la méthode.
Résultat de l'exécution :
Generic Animal makes a sound. Buddy barks.
Polymorphisme
Le polymorphisme de Lua est mis en œuvre via les métatables et la redéfinition de méthode. Lorsque des objets de types différents appellent la même méthode, Lua exécute différentes méthodes en fonction du type réel de l'objet.
Exemple
Person = {}
-- Ajouter un constructeur à la « classe »
function Person:new(name, age)
local obj = {} -- Créer une nouvelle table comme objet
setmetatable(obj, self) -- Définir la métatable pour indiquer qu'il s'agit d'une instance de la classe Person
self.__index = self -- Définir la métaméthode d'index pour pointer vers Person
obj.name = name
obj.age = age
return obj
end
-- Ajouter des méthodes
function Person:greet()
print("Bonjour, mon nom est " .. self.name)
end
-- Définir une sous-classe Student qui hérite de Person
Student = Person:new()
-- La sous-classe redéfinit la méthode de la classe parente
function Student:greet()
print("Salut, je suis étudiant et je m'appelle " .. self.name)
end
local person2 = Person:new("Charlie", 25)
local student2 = Student:new("David", 18)
-- Polymorphisme : des objets de types différents appellent la même méthode
person2:greet() -- Sortie "Bonjour, je m'appelle Charlie"
student2:greet() -- Sortie "Salut, je suis étudiant et je m'appelle David"
Bien que person2 et student2 appellent la même méthode greet, Lua appelle la version appropriée pour chacun car leurs types sont différents.
Résultat de l'exécution :
Hello, my name is Charlie Hi, I'm a student and my name is David
Autres concepts orientés objet
Encapsulation
L'encapsulation est généralement implémentée en encapsulant les données et les méthodes dans une table. Nous pouvons simuler l'encapsulation en contrôlant l'accès à la table, par exemple en utilisant des métaméthodes pour restreindre l'accès externe.
Exemple
Person = {}
-- Ajouter l'encapsulation : masquer les propriétés
function Person:new(name, age)
local obj = {}
setmetatable(obj, self)
self.__index = self
obj.name = name
obj.age = age
return obj
end
function Person:setName(name)
self.name = name -- Fournir une méthode pour modifier name
end
function Person:getName()
return self.name -- Fournir une méthode pour obtenir name
end
De cette façon, nous pouvons contrôler l'accès aux propriétés et simuler l'encapsulation.
Abstraction
L'abstraction désigne le fait de simplifier des choses complexes en masquant les détails inutiles. Bien que Lua lui-même n'ait pas le concept de classes, nous pouvons réaliser l'abstraction grâce à l'encapsulation.
Exemple
function Person:showInfo()
print("Nom : " .. self.name)
print("Âge : " .. self.age)
end