Metaclasses: singleton
Brincando na aula de PRJ no ISTCC-P, surgiu o assunto singleton (a página em inglês é muuuuito mais completa), que é quando queremos que uma determinada classe tenha somente uma instância, e esta instância seja retornada na tentativa de se criar novas conexões.
Complicado? Que nada! Vou dar o mesmo exemplo que o professor deu: imagine que seu programa tenha uma conexão com um banco de dados. Cada vez que uma função do programa tente criar uma conexão, esta receberá a conexão que já foi criada.
Simples! Em Java isso é feito «bloqueando» o construtor da classe e usando um método para intermediar esta requisição:
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. =)
Mas o grande problema aqui é que singleton não é transparente. É preciso estar atento e não usar
new
, que é a forma natural de se obter uma instância de classe. =POutras linguagens já têm uma abordagem diferente, possibilitando a criação de singleton transparente. As duas que comentarei aqui são Python e Ruby.
Ruby
Como Ruby não é meu forte, vou falar desta linguagem primeiro. É extremamente simples fazer singleton em Ruby:
requires 'singleton'
class Conexao
include Singleton
...
end
Bastou um
include
! Isto é incrivelmente simples, prático, mas tem um pequeno problema pra quem está tentando entender: como é que funciona?Fica aí a dúvida pros rubyistas explicarem nos comentários. =)
Singleton em Python usando herança
Já Python é outra história. Como sou programador Python (La Batalema Pitonisto, lembram?) posso usar melhor esta linguagem para mostrar como as coisas funcionam.
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 desta.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): #(trecho novo de código!)
return self
def __deepcopy__(self, memo=None):
return self
Então temos o atributo privado de classe
__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.
Singleton em 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 se preocupar 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 #(nova linha!)
cls.__deepcopy__ = lambda self, memo=None: self #(nova linha!)
def __call__(cls, *args, **kw):
if cls.__inst is None:
cls.__inst = super(unique, cls).__call__(*args, **kw)
return cls.__inst
Esta metaclasse é bela! Vamos dissecá-la:
O construtor chama seu
super
(construtor da classe pai, type
) e depois, a única coisa que faz de diferente é criar um atributo privado de classe __inst
. Só que este atributo é «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!
class Conexao(object):
__metaclass__ = type("__metaclass__", (autoprop, unique), {})
...
Complicou? É realmente... o uso de
type
não é tão intuitivo... =PMas há uma forma mais clara (aliás, eu prefiro) de fazer isso!
class Conexao(object):
class __metaclass__(autoprop, unique):
pass
...
Acho que é só isso! Comentários – ou melhor, complementos – por favor!
[]'s
Leitura recomendada: