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