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. =PRuby
É 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étodosclone
edup
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