Archives pour l'étiquette object

Classes d’objets

Retour à la partie 4

Dans cette partie nous allons étudier une notion assez pratique du Lua, celle de manipuler non plus des variables, mais des objets.

Si vous avez déjà étudié le Lua auparavant, vous devriez savoir que c’est un langage utilisant excessivement les tables indexées par clés. Cela veut dire que chaque élément d’un tableau (ou liste) est associé à une clé qui permet de la référencer. Par exemple la table suivante est indexée par des chaines de caractères :

tab = {"cle1"="valeur1", "cle2"="valeur2"}
print(tab["cle1"]) -- va afficher "valeur1"

En Lua, les clés comme les valeurs sont polymorphes, c’est à dire qu’on peut aussi bien stocker des chaines de caractères avec des tables ou encore des fonctions ou tout autre type de valeur :

tab = {
  [1]="exemple",
  "2"="de",
  [function() return 3 end]="table",
  [false]="polymorphe"
}
 
for k, v in pairs(tab) do
  print(k, v)
end

Affichera :

1                 exemple
2                 de
function: 10f6840 table
false             polymorphe

Même l’environnement global ( _G ) est une table !

C’est donc dans cette optique là que l’on peut manipuler le Lua sous forme de langage objet, avec chaque objet une table et chacun des éléments de cet objet une méthode ou une propriété. Par la suite, si l’on veut créer un nouvel objet, il suffira de copier la table « modèle » (concept des meta-table). Pour créer des classes d’objets hérités on procédera de la même manière, sauf qu’on modifiera certaines méthodes.

myObject = {
    nom="toto",
    age=21,
    ChangerNom=function(self, nom)
        if #nom > 1 then
            self.nom = nom
        end
    end,
    ChangerAge=function(self, age)
        if age > 1 then
            self.age = age
        end
    end
}

Et accéder très facilement à chacun des éléments :

print(myObject.age) -- affiche 21
myObject:ChangerAge(18) -- identique à myObject.ChangerAge(myObject, 18)
print(myObject.age) -- affiche 18

Il faut comprendre cependant qu’il n’y a pas de notion d’espaces publiques et privés comme en C++/C#/Java, car c’est simplement une représentation, pas une réelle implémentation.

De cette manière vous devez enfin comprendre pourquoi on écrit platform.window:width() et non une variable ressemblant à « GetWindowWidth() ». En effet, width() est une méthode de l’objet window qui est une propriété de l’objet platform. Pareil pour platform.gc() que l’on retrouve abrégé en gc. En réalité, gc est un objet et platform.gc() en est son constructeur. D’un côté, c’est donc la même chose (ils ont tous les deux les mêmes méthodes) mais de l’autre non (ils ont des propriétés quelque peu différentes).

Nous allons donc, pour illustrer ce principe, créer ensemble un script Lua qui nous permettra de créer une et une seule fois une classe Forme ayant plusieurs méthodes dont un constructeur permettant de créer plusieurs formes.

Dans l’API Lua de la TI-Nspire, on trouve une méthode « class() » qui ne provient pas du Lua. En réalité, cette méthode économise certaines lignes qui peuvent paraître incompréhensibles.

Ces lignes sont :

Account = {}
Account.__index = Account
 
function Account.create(balance)
    local acnt = {}             -- our new object
    setmetatable(acnt,Account)  -- make Account handle lookup
    acnt.balance = balance      -- initialize our object
    return acnt
end
 
function Account:withdraw(amount)
    self.balance = self.balance - amount
end
 
-- create and use an Account
acc = Account.create(1000)
acc:withdraw(100)

Source : http://lua-users.org/wiki/SimpleLuaClasses

Maintenant, la même chose en utilisant class() :

Account = class()
 
function Account:init(balance)
    self.balance = balance      -- initialize our object
end
 
function Account:withdraw(amount)
    self.balance = self.balance - amount
end
 
-- create and use an Account
acc = Account(1000)
acc:withdraw(100)

Avouez que cela est plus clair ?
Il est à noter également que init() est un constructeur. Le nom de ce constructeur est contraint par la méthode class() qui appelle ce dernier pour compléter la construction avec setmetatable().

Nous pouvons donc créer très facilement notre classe Forme :

Forme = class()
 
-- Constructeur
function Forme:init(x, y, image)
    self.xpos = x
    self.ypos = y
    self.pic = image
end
 
-- Dessiner la forme
function Forme:paint(gc)
    gc:drawImage(self.pic, self.xpos, self.ypos)
end
 
-- Repositionner l'objet en indiquant les nouvelles coordonnees
function Forme:setxy(newx, newy)
    if newx > xmin and newx < xmax and newy > ymin and newy < ymax then
        self.xpos = newx
        self.ypos = newy
   end
end
 
-- Repositionner l'objet en indiquant le deplacement a effectuer
function Forme:move(dx, dy)
    local newx,newy
    self:setxy(self.xpos + dx,self.ypos + dy)
end

Vous aurez surement remarqué l’utilisation de variables globales, xmin, ymin xmax et ymax.

xmin = 20
ymin = 30
xmax = 250
ymax = 200

Nous pouvons enfin jouer avec notre classe Forme ! Pour cela nous allons créer deux images et les convertir en TI.Image en utilisant l’outil préliminaire de développement :

carre_rouge = image.new("\010\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\016\000\001\000\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252")
smiley = image.new("\016\000\000\000\018\000\000\000\000\000\000\000 \000\000\000\016\000\001\000\156\243\156\243\156\243\189\247\213\222\235\193\136\181g\181\135\181g\181\137\185O\202\189\247\156\243\156\243\156\243\156\243\156\243{\239O\206\168\222F\243D\243d\247d\247#\243\004\235E\214\170\1858\231\156\243\156\243\156\243\213\222m\210\231\230d\247\132\251\195\255\131\255\131\255\131\255C\251\003\247\132\226\233\193r\210z\239\156\243\204\189\198\226\132\247\166\218\007\198\198\226\195\255\131\255#\243\231\197\007\198\002\251\131\230\135\185\246\222\212\218g\210D\243\166\218\166\218\197\226D\239\131\255\131\255C\247\165\218d\222\006\206\194\242\196\205O\206\170\185\005\239\131\255\197\226D\239\131\255\131\255\131\255\131\255C\251\034\255\002\255E\218\226\250b\234\167\193G\173C\247\131\255\131\255\130\218\001\198\162\222\131\255c\251\002\243\161\197\161\197\226\250\226\250\162\246\133\193f\181#\243C\251\131\255\194\193\194\156\002\202C\251\034\255\162\234\194\156\194\156\193\250\193\254\193\254\133\193g\181\003\247c\251\034\255\196\230e\218\196\234\034\255\034\255\226\242%\218%\218\226\250\226\250\161\254\133\193&\169\226\242\034\255\034\255\034\255\034\255\034\255\002\255\002\255\193\254\193\254\226\250\161\254\161\254\161\254d\189G\173\163\234\002\255\226\242\194\242\163\234\162\242\162\242\162\242\130\238\130\242B\242\130\242\162\246B\242\133\193\014\198E\214\194\242f\181\198\156\231\156\231\156\231\156\231\156\231\156\231\156\198\156\231\156B\242\227\213\235\197Y\235\136\185\130\238\193\254\132\189s\206\222\251\222\251\222\251\222\251{\239\198\156\130\242\129\254d\189\179\214\156\243\237\193\196\205\162\242\162\242\198\201)\165)\165)\165)\165H\173B\242\129\254\227\213\169\193\023\227\156\243z\239\137\185\034\226\162\246\130\242B\234\034\230\034\230B\234\034\234\129\254\130\242D\181\213\222\156\243\156\243\156\243\023\227\201\197\002\222\162\246\161\254\129\254\161\254\129\254\129\250\034\234d\189\147\214\156\243\156\243\156\243\156\243\156\243\246\222\011\206\196\205\002\222B\238\034\238\034\230\196\209\165\197\147\214{\239\156\243\156\243\156\243\189\247\156\243\156\243\023\227p\210\011\206\198\205\198\205\232\205O\210\147\214\156\243\156\243\156\243\156\243")
forme1 = Forme (xmin + 20, ymin + 20, smiley)
forme2 = Forme (xmax - 30, ymax - 30, carre_rouge)

Dorénavant il ne nous reste plus qu’à créer la structure principale en utilisant divers évènements afin de tester toutes les possibilités de la classe que nous venons de créer.

-- Affichage
function on.paint(gc)
    gc:setColorRGB(231, 231, 231)
    gc:fillRect(xmin, ymin, xmax-xmin+15, ymax-ymin+15)
    forme1:paint(gc)
    forme2:paint(gc)
    gc:setColorRGB(0, 0, 255)
    gc:setFont("sansserif" , "b", 11)
    x = gc:drawString("(" .. forme1.xpos..", ".. forme1.ypos .. ")", 50, 0, "top")
end
-- Permet de deplacer forme1 avec les fleches directionnelles
function on.arrowLeft()
    forme1:move(-5, 0)
end
 
function on.arrowRight()
    forme1:move(5, 0)
end
 
function on.arrowUp()
    forme1:move(0, -5)
end
 
function on.arrowDown()
    forme1:move(0, 5)
end
-- Permet de choisir l'emplacement de forme2 avec la souris
function on.mouseDown(wx, wy)
    forme2:setxy(wx, wy)
end

Et voilà le résultat !