Borg

O pattern Borg1 é uma alternativa ao pattern Singleton, mas não é um pattern listado no GoF. Enquanto que o pattern singleton se preocupa em manter uma instancia única de um objeto, o pattern borg se preocupa em compartilhar o estado entre todas as instancias de uma classe, mantendo assim um estado único (por isso ele também é conhecido como monostate). Uma vantagem direta no uso do pattern borg com relação ao singleton é a possíbilidade utilizar heranças, porém, pode ser mais custoso em termos de uso de memória.

Implementação

A implementação do pattern borg é tão simples quanto a implementação do pattern singleton. Toda classe customizada no python possuí o atributo especial __dict__2 e esse atributo é responsável por armazenar o estado do objeto. Sendo assim, basta compartilhar o estado do atributo __dict__ entre todas as instancias que forem criadas.

# my_module.py
class Borg:

    _shared_state = {}

    def __new__(cls):
        inst = super().__new__(cls)
        inst.__dict__ = cls._shared_state
        return inst

A inteligência desse exemplo fica por conta da linha inst.__dict__ = cls._shared_state. Com isso estamos mudando o atributo __dict__ de todas instancias criadas da classe Borg para que possuam o mesmo valor, o dicionário armazenado em Borg._shared_state3, ou seja, nosso estado único ou estado compartilhado. Como dicionários em python são mutables, qualquer alteração em uma instancia, será refletida na variável Borg._shared_state que é para onde todos atributos __dict__ de todas instancias de Borg estão apontando.

>>> from my_module import Borg
>>> foo = Borg()
>>> bar = Borg()
>>> foo.some_attribute = 'some value'
>>> bar.some_attribute
'some value'
>>> foo is bar
False

Mesmo sendo objetos diferentes, o comportamento será sempre o mesmo.

Herança

Como dito anteriormente, o pattern borg resolve o problema do pattern singleton em relação ao uso de heranças, porém, é preciso se atentar a algumas questões.

# my_module.py
class Borg:

    _shared_state = {}

    def __new__(cls):
        inst = super().__new__(cls)
        inst.__dict__ = cls._shared_state
        return inst

class Foo(Borg):
    pass

No caso de uma herança simples, o estado seria compartilhando tanto entre a casa mãe (no exemplo acima a classe Borg) quanto a classe filha (Foo).

>>> from my_module import Borg, Foo
>>> borg = Borg()
>>> foo = Foo()
>>> borg.some_attribute = 'some value'
>>> foo.some_attribute
'some value'

Em muitos casos esse não é o cenário esperado, pois deixa de fazer sentido a herança, porém, é possível resolver essa questão usando sobreposição4 para substituir o estado compartilhado.

# my_module.py
class Borg:

    _shared_state = {}

    def __new__(cls):
        inst = super().__new__(cls)
        inst.__dict__ = cls._shared_state
        return inst

class Foo(Borg):
    _shared_state = {}

No exemplo acima a variável de classe _shared_state que representa nossa estado compartilhado, foi sobrescrita na classe Foo o que significa que agora as instancias de Foo possuem um estado compartilhado próprio e não compartilham mais o _shared_state da classe Borg.

>>> from my_module import Borg, Foo
>>> borg = Borg()
>>> foo = Foo()
>>> borg.some_attribute = 'some value'
>>> foo.some_attribute
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'some_attribute'
>>> bar = Foo()
>>> bar.some_attr = 'some val'
>>> foo.some_attr
'some val'
>>> borg.some_attr
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Borg' object has no attribute 'some_attr'

Note que os objetos foo e bar compartilham o mesmo estado, porém, o objeto borg possui um estado diferente, mesmo a classe Foo sendo uma classe filha de Borg.

Além de herança, mais algum motivo?

O renomado autor Alex Martelli5 defende que na maioria dos casos não estamos preocupados com a identidade do objeto, mas sim com seu estado e seu comportamento6, por essa razão o pattern borg se torna mais apropriado do que o pattern singleton.


  1. O nome borg é uma referencia a uma espécie da série star trek, onde suas decisões são tomadas por uma unica mente coletiva. 

  2. "A class has a namespace implemented by a dictionary object. Class attribute references are translated to lookups in this dictionary, e.g., C.x is translated to C.__dict__["x"]" Data model, Python Documentation. https://docs.python.org/3.4/reference/datamodel.html 

  3. Note que não foi feita uma cópia de Borg._shared_state mas sim, foi alterado a referência do atributo __dict__ para apontar para Borg._shared_state

  4. Sobreposição é um recurso de programação orientada a objetos que permite que classes filhas (ou subclasses) reescrevam valores ou comportamentos da classe mãe (ou superclasse). Quando utilizamos o método especial __new__ estamos fazendo uma sobreposição em nossas classes com relação a classe object do python. 

  5. Alex Martelli é o autor dos livros Python in a Nutshell e Python Cookbook

  6. "Who cares about identity -- it's state (and behavior) we care about!" Singleton? We don't need no stinkin' singleton, Alex Martelli. http://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/