E como fica o Singleton no Python?

Ultimamente tenho passado a dar mais atenção ao Python e tenho gostado muito da experiência. Obviamente que, para qualquer desenvolvedor, a transição de uma linguagem para outra pode ocasionar alguns choques. Vejo isso como algo positivo, simplesmente por que, você não pode codificar em um linguagem como codificaria em outra. Em outras palavras, você não pode desenvolver em python da mesma forma que desenvolve em delphi. Desenvolver desta forma é um erro muito comum e que pode atrapalhar bastante no processo de aprendizado.

O mais recente choque desses que tive foi com relação ao pattern Sigleton (GoF). Isso porque, aparentemente em python existe outro pattern semelhante, que na prática funciona da mesma maneira, o pattern Borg.

Se no pattern Singleton, garantimos somente uma instancia de um determinado objeto, no Borg podemos ter N instancias do mesmo objeto, porém, todas essas instancias compartilham o mesmo estado. O que isso significa? Significa que, em um objeto de uma classe Borg, se você atribuir 10 para o atributo x, você está alterando o estado de todos os objetos instanciados dessa classe.

Para descomplicar, vamos ver os dois padrões na prática, começando pelo Borg:

class Borg:
    __shared_state = {}

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, value):
        self.__x = value

    def __init__(self):
        self.__dict__ = self.__shared_state

O atributo __shared_state é um atributo de classe e o Self.__dict__ consiste em uma estrutura interna do Python que armazena o estado do objeto. Em uma forma simplista de dizer, o código acima faz com que a cada nova instancia da classe Borg (__init__), o estado do objeto passe a ser controlado pelo atributo __shared_state.

Já o Singleton não possui nenhuma novidade, a inteligência toda fica por conta do método instance().

class Singleton:
    __instance = None

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, value):
        self.__x = value

    @staticmethod
    def instance():
        if not Singleton.__instance:
            Singleton.__instance = Singleton()
        return Singleton.__instance

Em termos de uso, como mencionei no começo do post, não temos muitas diferenças, veja um exemplo:

s1 = Singleton.instance()
s2 = Singleton.instance()
s1.x = 10

b1 = Borg()
b2 = Borg()
b1.x = 20

print('Singleton')
print('s1.x={} | s2.x={}'.format(s1.x, s2.x))
print('id(s1)={} | id(s2)={}'.format(id(s1), id(s2)))
print('-------------------')
print('Borg')
print('b1.x={} | b2.x={}'.format(b1.x, b2.x))
print('id(b1)={} | id(b2)={}'.format(id(b1), id(b2)))

A saída do código acima será a seguinte:

Singleton
s1.x=10 | s2.x=10
id(s1)=30928456 | id(s2)=30928456
-------------------
Borg
b1.x=20 | b2.x=20
id(b1)=30928512 | id(b2)=30928624

Veja que nos objetos Borg, o id retornado é diferente, o que significa que estamos lidando com instancias distintas, porém, o valor do atributo X é o mesmo para as duas instancias.

Uma vantagem do Borg é que o seu funcionamento se estende para as suas subclasses, porém, as vezes o que realmente queremos é somente uma instancia de uma determinada classe.

Independente de qual é o melhor, conhecer os dois e estar pronto para usá-los ou interpretá-los no momento certo é com certeza a melhor opção.

Sobre Diego Garcia

Analista/Desenvolvedor Delphi desde 2008, bacharel em Ciência da Computação e entusiasta de metodologias ágeis e engenharia de software.
Esse post foi publicado em Programação e marcado , , , . Guardar link permanente.

Deixe um comentário