terça-feira, 27 de janeiro de 2009

Singleton

Um design pattern bastante conhecido na engenharia de software é singleton.

Singleton é quando uma classe possui apenas uma instância e não se deseja que em uma mesma aplicação haja mais de uma.

Exemplos de classes desejadamente singleton são pools e carregadores.

Em C++ a saída para criar uma classe singleton é tornar protegido seu método construtor e criar um método getInstance para retornar a instância única:
class Loader {
protected:
Loader();

public:
~Loader();

static Loader& getInstance();
}


O método getInstance deve guardar uma referência estática para a instância:
static Loader& Loader::getInstance() {
static Loader *instance = 0;
if (!instance)
instance = new Loader();
return *instance;
}


Java utiliza a mesma abordagem, já outras linguagens, como Perl, Python e Ruby têm uma abordagem bem mais elegante.

Singleton em Python


A ideia de singleton em Python – Perl e Ruby – é usar o próprio construtor da classe para obter a instância.

Por exemplo, em vez de (Java):
Image character = Loader.getInstance().getImage("char.png");


Teremos (Python):
character = Loader().getImage("char.png")


Há três formas de implementar singleton em Python: 1implementando diretamente na classe, 2herdando um classe pai ou 3usando metaclasse (o mais divertido!).

Implementação direta na classe


Você pode implementar uma classe singleton diretamente, usando os metamétodos __new__ (construtor real) e __init__ (construtor de inicialização).

A classe ainda precisa ter um atributo de classe para armazenar a instância.

A ideia mais simples é implementar __new__ para devolver a instância em vez de uma nova a cada vez:
class Loader:

__instance = None
__initialized = False


def __new__(cls, *args, **keyw):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
return cls.__instance


Repare no atributo __initialized… isso é porque ainda temos um problema: o construtor de inicialização (__init__) será chamado novamente a cada tentativa de obter a instância.

Então precisamos verificar se a inicialização já foi executada:
    def __init__(self, *args, **keyw):
if not self.__initialized:
self.__initialized = True
# Restante do método...


Pronto! Nossa classe já é singleton.

Herança


Podemos usar uma classe pai que implemente unicidade e estendê-la:
class Singleton:

__instance = None
__initialized = False

def __new__(cls, *args, **keyw):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
return cls.__instance

def __init__(self, *args, **keyw):
if not self.__initialized:
self.__initialized = True
self._init(*args, **keyw)


Assim, para implementar uma classe singleton:
class Loader(Singleton):

def _init(self, *args, **keyw):
# Este será o construtor...


A vantagem desta abordagem é sua capacidade de reaproveitamento: é possível reutilizar a classe Singleton como classe pai de cada nova classe singleton.

No entanto traz dois inconvenientes: primeiro os métodos podem conflitar com métodos de outras classes pai em herança múltipla; segundo é preciso prestar atenção à implementação estranha (uso de _init em vez de __init__) e ter cuidado com a sobrescrita de métodos.

A solução para tornar isso mais transparente é usar uma metaclasse.

Metaclasse


Metaclasse é um classe cujas instâncias são classes. Vamos criar uma metaclasse cujas classes sejam singleton:
class singleton(type):

def __init__(cls, name, base, dict):
super(singleton, cls).__init__(name, base, dict)
cls.__instance = None
cls.__copy__ = lambda self: self
cls.__deepcopy__ = lambda self, memo=None: self

def __call__(cls, *args, **keyw):
if cls.__instance is None:
cls.__instance = \
super(singleton, cls).__call__(*args, **keyw)
return cls.__instance


Agora vem a beleza da metaclasse: para criar uma classe singleton basta fazer:
class Loader:
__metaclass__ = singleton


Nada mais! Todo o resto da classe pode ser implementado sem preocupações.

A única dúvida que pode ocorrer é: e se a classe tiver outra metaclasse?

É fácil resolver! Por exemplo: imagine uma classe singleton que implemente autopropriedades:
class Loader:

class __metaclass__(autoprop, singleton):
pass


**

Está aqui outra dica! Mais sobre singleton pode ser encontrado nas Reflexões de Monte Gasppa e Giulia C..

[]'s
Cacilhas, La Batalema
blog comments powered by Disqus