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.
-
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. ↩
-
"A class has a namespace implemented by a dictionary object. Class attribute references are translated to lookups in this dictionary, e.g.,
C.xis translated toC.__dict__["x"]" Data model, Python Documentation. https://docs.python.org/3.4/reference/datamodel.html ↩ -
Note que não foi feita uma cópia de
Borg._shared_statemas sim, foi alterado a referência do atributo__dict__para apontar paraBorg._shared_state. ↩ -
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 classeobjectdo python. ↩ -
Alex Martelli é o autor dos livros Python in a Nutshell e Python Cookbook. ↩
-
"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/ ↩