Semestre passado estávamos conversando sobre
singleton (veja também a
página em inglês), que é quando queremos que uma determinada classe tenha somente uma instância, e esta instância seja retornada na tentativa de se criar novas instâncias.
Na época escrevi um
artigo sobre o assunto e agora resolvi atualizá-lo aqui.
A idéia inicial era demonstrar como é implementado
singleton em algumas linguagens e vou seguir novamente o mesmo princípio.
Java
Em Java
singleton é feito «bloqueando» o construtor da classe e usando um método para intermediar esta requisição (aliás esta é a abordagem da maioria das linguagens):
class Conexao {
//Atributos
…
private static Conexao inst = null;
//Metodos
private Conexao() {
// O construtor é privado!
…
}
public static Conexao nova() {
// Este método fará as vezes do construtor
if (inst == null)
inst = new Conexao();
return inst;
}
…
}
Então, para criar uma conexão, não será usado
new, mas o método
nova():
Conexao conn = Conexao.nova();
Funciona. =)
O grande problema é que
singleton não é transparente em Java. É preciso estar atento e não tentar usar
new, que é a forma natural de se obter uma instância de classe. =P
Ruby
É extremamente simples fazer
singleton em Ruby:
requires 'singleton'
class Conexao
include Singleton
…
end
Bastou um
include! Isto é incrivelmente simples, prático. Quem me explicou seu funcionamento foi o
Walter:
A solução de Ruby é parecida com Java e Python.
A inclusão do módulo singleton torna o construtor da classe privado. A instância é criada no momento de instanciação. Os métodos clone e dup retornam erro.
E isso tudo escondidinho, em 343 linhas de código invísiveis ao usuário :)
Python (usando herança)
Uma forma de fazer
singleton em Python é usando herança. Assim podemos criar uma classe
Singleton que implemente a funcionalidade (
feature) desejada e herdar as demais classes dela.
Bem, em Python o construtor é
__init__(), mas há um método que é chamado antes do construtor, que é
__new__(), e funciona de forma muito parecida com
new de Perl. Ele cria a nova instância e a retorna.
Na verdade, em Python quando fazemos:
a = X(b, c)
Por trás o que está acontecendo é:
a = X.__new__(X, b, c)
a.__init__(b, c)
Então podemos sobrescrever este método para termos
singleton. A idéia é que, se já houver uma instância, o
__new__() retorne esta instância em vez de uma nova:
class Singleton(object):
__inst = None
def __new__(cls, *args, **kw):
if cls.__inst is None:
cls.__inst = object.__new__(cls)
return cls.__inst
def __copy__(self):
return self
def __deepcopy__(self, memo=None):
return self
Então temos o atributo de classe privado
__inst que contém nada (
None) na criação da classe, mas depois será uma referência à instância única.
O método
__new__() está preparado para receber argumentos genéricos (
*args,
**kw), mas não fará nada com eles – é problema do
__init__(). A função do
__new__() é verificar se já existe a instância única, se não existe, cria, depois retorna ela.
Podemos usar a herança desta forma:
class Conexao(Singleton):
…
Ou seja, funciona
exatamente como em Ruby.
Agora vamos à crítica!
O grande problema aqui é justamente a sobrescrita um método geralmente esquecido…
Isso pode gerar uma série de problemas quando surge a herança múltipla ou quando o método é novamente sobrescrito – ou foi também sobrescrito por outra classe pai.
Para contornar estes problemas uma boa saída é o uso de metaclasses.
Python usando metaclasse
Em Python classes são objetos! São instâncias de
type. Metaclasses são classes filhas de
type, portanto suas instâncias também são classes.
Podemos criar uma metaclasse que tenha procedimentos que precedam o construtor e o chamem só se for preciso.
Assim as classes instância da metaclasse podem ser filhas de outras classes e possuir até herança múltipla sem problemas com sobrescrita de métodos – pois todo tratamento é realizado num escopo ainda mais protegido.
Numa metaclasse o método que precede o construtor das classes instância é – por
motivos óbvios para programadores Python –
__call__().
Vamos criar então a metaclasse! Vou chamar de
unique em vez de
singleton (Por quê? Porque eu gosto, ora pois!):
class unique(type):
def __init__(cls, name, base, dict):
super(unique, cls).__init__(name, base, dict)
cls.__inst = None
cls.__copy__ = lambda self: self
cls.__deepcopy__ = lambda self, memo=None: self
def __call__(cls, *args, **kw):
if cls.__inst is None:
cls.__inst = super(unique, cls).__call__(*args, **kw)
return cls.__inst
O construtor chama o construtor da classe pai (
super) e então a única coisa diferente que ele faz é a criação de um atributo privado de classe
__inst e de duas funções para tratar as cópias. Só que este atributo
__inst é «mais privado» do que os normais. =)
Aqui o escopo para o atributo privado não é a classe, mas
unique (a metaclasse). Podem imaginar o nível de proteção disso? É como em C++, só que funciona. =)
Bem, o método
__call__() faz exatamente o mesmo que
__new__() no exemplo que usa herança, só que de forma mais eficiente. A implementação disso poderia ser:
class Conexao(object):
__metaclass__ = unique
…
Simples! E sem os problemas que poderiam vir com o uso de herança.
Mas aí vem um espírito de porco qualquer e pergunta:
—
Peraí! E se eu quiser usar também outra metaclasse, como autoprop?Ahá! Não é tão complicado assim! Veja só:
class Conexao(object):
class __metaclass__(autoprop, unique):
pass
…
Lua
Em Lua, assim como em Perl, o construtor é um método de classe, o que facilita muito nosso trabalho, pois cada instância deve ser criada nele.
Uma classe é uma metatabela cuja chave
__index referencia a si mesma:
local Conexao = {}
Conexao.__index = Conexao
O construtor padrão (nu e cru) costuma ser:
function Conexao:new(o)
o = o or {}
return setmetatable(o, self)
end
Vamos então criar uma função
unique (porque eu gosto!) que retorne uma classe cujo construtor retorne sempre a mesma instância (usando
closure para isso):
local function unique()
local inst
local cmt = {}
cmt.__index = cmt
cmt.new = function(self, o)
if not inst then
o = o or {}
inst = setmetatable(o, self)
if self.__init then
inst.__init()
end
end
return inst
end
return cmt
end
Então podemos criar nossa classe
Conexao assim:
local Conexao = unique()
Os demais métodos podem ser implementados normalmente e, caso seja necessário implementar alguma coisa no construtor, é possível implementando o método
de instância __init().
[]'s
Rodrigo Cacilhas