Orientação a objetos em Lua
Dando continuidade a orientação a objetos em Perl, vamos agora falar sobre orientação a objetos em Lua.
Lua é uma linguagem de programação intrigante... sua orientação a tabelas (e metatabelas, metamétodos...) é prática e versátil.
Assim como quase tudo em Lua, a implementação de orientação a objetos também é baseada em tabelas e metatabelas.
Metatabelas
Metatabelas são tabelas que controla o comportamento de outras estruturas de dados.
Por exemplo, executar o seguinte no prompt do interpretador:
lua> nome = "La Batalema Pitonisto"
lua> print(nome:upper())
LA BATALEMA PITONISTO
De onde veio esse
upper()
? Daqui:lua> print(getmetatable(nome))
table: 0x806b2a8
Ah! Há uma metatabela associada às strings!!!
Repare nisso:
lua> print(getmetatable(nome).__index == string)
true
Olha só! A chave
__index
da metatabela das strings é o módulo string
!Isto significa que:
nome:upper() == nome.upper(nome) == string.upper(nome)
Chave __index
A chave
__index
da metatabela pode ser uma tabela ou uma função e indica o que deve acontecer quando houver uma tentativa de leitura de uma chave que a estrutura de dados original não possua.Por exemplo, o objeto referenciado pela variável
nome
(uma string) não possui a chave upper
, então quando tentamos acessar esta chave, o sistema procurar pela chave na tabela referenciada pela chave __index
da metatabela, que é string
.Se o valor da chave
__index
da metatabela for uma função, esta função deve ter dois argumentos: 1º o objeto original, 2º a chave; a função retorna o esperado para a chave.Por exemplo, se queremos que uma tabela retorne o código ASCII do primeiro caráter do nome da chave informada, podemos usar uma função:
mt = {
__index = function (t, k)
return k:byte()
end
}
var = setmetatable({}, mt)
Então, por exemplo,
var.b
retornará 98 (o código ASCII para b
).Esta chave é importantíssima para a orientação a objetos.
Chaves __newindex
e __mode
Não vamos ver estas chaves neste artigo, mas é interessante conhecê-las.
A chave
__newindex
recebe uma função (argumentos: o objeto original, a chave, o valor atribuído) e é chamada quando se tenta atribuir um valor a uma chave que não existe. Sem esta chave da metatabela, a chave simplesmente seria criada no objeto e o valor atribuído a ela, com esta chave da metatabela, a função é executada e decide o que fazer.A chave
__mode
decide se chaves e valores do objeto original serão referências fracas ou não. O padrão é não.Classes e construtores
Em orientação a objetos, classe é um molde para a criação de novos objetos.
Em Lua, classe em geral é uma metatabela onde a chave
__index
aponta para ela própria. Algo assim:mt = {}
mt.__index = mt
Isso faz com que as chaves da metatabela sejam padrões para as tabelas que a receberem como metatabela, ou seja, a metatabela se torna um molde para outras tabelas. As tabelas que fazem uso deste molde são chamadas instâncias.
As funções de uma classe/instância são chamadas métodos e sempre recebem implícita ou explicitamente como primeiro argumento a classe ou instância que faz a chamada.
Algumas linguagem são extremamente implícitas, escondendo a referência à classe ou instância.
Lua pode chamar um método passando a instância (ou classe) implícita ou explicitamente.
Exemplo de uma chamada explícita:
login = login.lower(login)
Agora a mesma chamada, mas passando a instância implicitamente:
login = login:lower()
Há um método especial chamado construtor, que é executado sempre que uma nova instância é criada.
Em algumas linguagens, o construtor é executado pela instância após esta ter sido criada. Nestas linguagens o construtor costuma ter o mesmo nome da classe (exceto Python, onde é chamado
__init__()
).Em outras linguagens, o construtor é executado pela classe, como um método de classe, e retorna a instância. Nestas linguagens o construtor geralmente é chamado
new()
(como em Perl).Lua usa a segunda abordagem.
Então um construtor simples pode ser:
function mt:new(o)
o = o or {}
return setmetatable(o, self)
end
O construtor aqui recebe como argumento uma tabela que servirá de referência para a criação da instância.
O primeiro comando garante que o argumento
o
é uma tabela, e o segundo associa a metatabela ao objeto, retornando-o.Como
new()
é um método de classe, self
representa a classe. Se fosse um método de instância (que deve ser chamado pela instância), self
representaria a instância.Outros métodos
Podemos criar outros métodos. Por exemplo, poderíamos querer que um somatório dos elementos numéricos da tabela seja retornado para o método
soma()
:function mt:soma()
local s = 0
table.foreachi(self, function (i, e)
if type(e) == "number" then
s = s + e
end
end)
return s
end
Então podemos criar um objeto com alguns valores numéricos e retornar seu somatório:
var = mt:new { 2, 4, 6 }
ret = var:soma()
Aqui a variável
ret
receberá 12 (a soma dos elementos).Metamétodos
Há alguns metamétodos interessantes, que agem junto a operadores.
Não vou entrar em detalhes, apenas vou citar alguns:
__add
– gerencia operador de adição;__sub
– gerencia operador de subtração;__mul
– gerencia operador de multiplicação;__div
– gerencia operador de divisão;__unm
– gerencia operador unário de negação;__eq
– gerencia operador de igualdade;__lt
– gerencia operadores menor que e igual ou maior;__le
– gerencia operadores menor ou igual e maior que;__pow
– gerencia operador de potência;__tostring
– gerencia conversão para string;__tonumber
– gerencia conversão para número.
Herança
Imagine agora que queiramos aproveitar o que já programamos...
Queremos outra classe que além de devolver a soma, também devolva o produto, mas sem modificar a classe original.
Para isto herdamos uma nova classe. Mas como?
Muito simples: basta instanciar a classe pai normalmente, modificar a instância e usar esta instância como uma nova classe:
nmt = mt:new()
function nmt:produto()
local p = 1
table.foreachi(self, function (i, e)
if type(e) == "number" then
p = p * e
end
end)
return p
end
var = nmt:new { 2, 4, 6 }
print(var:soma(), var:produto())
Serão exibidos 12 e 48.
Há uma forma mais avançada de herança, chamada herança múltipla, que acontece quando uma classe é herdeira de mais de uma classes pai.
Algumas linguagens, como Java, não permitem herança múltipla, devido a problemas de gerenciamento de polimorfismo, outras, como Perl e C++, confiam que o programador seja competente o suficiente para lidar com polimorfismos. Outras ainda, como Python, oferecem ferramentas para lidar com isso.
Em Lua, você escolhe!
A implementação de herança múltipla em Lua é complicada e mereceria um post por si só, portanto, quem quiser dar uma olhada, veja multiple inheritance no PiL.
Conclusão
Este foi apenas um artigo superficial sobre como implementar orientação a objetos em Lua. Espero que tenha sido elucidativo.
As melhores referências sobre Lua são o PiL e, preferencialmente, o PiL2.
[]'s