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
…
endBastou 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étodoscloneedupretornam 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 selfEntã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.__instO 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 = ConexaoO construtor padrão (nu e cru) costuma ser:
function Conexao:new(o)
o = o or {}
return setmetatable(o, self)
endVamos 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
endEntã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
CC-BY: Os textos deste blog podem ser reporduzidos contanto que sejam informados autor e origem.