¿Por qué el patrón Borg es mejor que el patrón Singleton en Python?

82

¿Por qué el patrón Borg es mejor que el patrón Singleton ?

Pregunto porque no veo que resulten en nada diferente.

Borg:

class Borg:
  __shared_state = {}
  # init internal state variables here
  __register = {}
  def __init__(self):
    self.__dict__ = self.__shared_state
    if not self.__register:
      self._init_default_register()

Único:

class Singleton:
  def __init__(self):
    # init internal state variables here
    self.__register = {}
    self._init_default_register()

# singleton mechanics external to class, for example this in the module
Singleton = Singleton()

Lo que quiero mostrar aquí es que el objeto de servicio, ya sea implementado como Borg o Singleton, tiene un estado interno no trivial (proporciona algún servicio basado en él) (quiero decir que tiene que ser algo útil, no es un Singleton / Borg solo para divertido).

Y este estado debe iniciarse. Aquí, la implementación de Singleton es más sencilla, ya que tratamos a init como la configuración del estado global. Me resulta extraño que el objeto Borg tenga que consultar su estado interno para ver si debería actualizarse.

Se vuelve peor cuanto más estado interno tienes. Por ejemplo, si el objeto tiene que escuchar la señal de eliminación de la aplicación para guardar su registro en el disco, ese registro solo debe hacerse una vez también, y esto es más fácil con un Singleton.

u0b34a0f6ae
fuente
1
¿Patrón Borg? ^ _ ^ Escuché por primera vez de él como c2.com/cgi/wiki?MonostatePattern
Jeffrey Hantin
9
¿Monoestado? Somos los Martellis. Decimos Borg.
u0b34a0f6ae

Respuestas:

66

La verdadera razón por la que Borg es diferente se reduce a las subclases.

Si subclasifica un borg, los objetos de la subclase tienen el mismo estado que los objetos de las clases principales, a menos que anule explícitamente el estado compartido en esa subclase. Cada subclase del patrón singleton tiene su propio estado y, por lo tanto, producirá diferentes objetos.

Además, en el patrón singleton los objetos son en realidad iguales, no solo el estado (aunque el estado es lo único que realmente importa).

David Raznick
fuente
1
> También en el patrón singleton los objetos son en realidad iguales, no solo el estado (aunque el estado es lo único que realmente importa). Por qué es eso algo malo?
agiliq
buena pregunta uswaretech, es parte de mi pregunta anterior. ¿Qué es eso dicho tan malo?
u0b34a0f6ae
2
No dije que fuera algo malo. Fue una observación sin opinión sobre las diferencias. Perdón por la confusion. A veces, el singleton será mejor de hecho, si, por ejemplo, verificas los objetos id por id (obj), aunque esto sea raro.
David Raznick
Entonces, ¿cómo Noneen Python es un Singleton y no un ejemplo de patrón Borg?
Chang Zhao
@ChangZhao: porque en el caso de None necesitamos tener la misma identidad, no solo compartir estado. Hacemos x is Nonecomprobaciones. Además, None es un caso especial ya que no podemos crear subclases de None.
olivecoder
23

En Python, si desea un "objeto" único al que pueda acceder desde cualquier lugar, simplemente cree una clase Uniqueque solo contenga atributos estáticos, @staticmethodsy @classmethods; podrías llamarlo el Patrón Único. Aquí implemento y comparo los 3 patrones:

Único

#Unique Pattern
class Unique:
#Define some static variables here
    x = 1
    @classmethod
    def init(cls):
        #Define any computation performed when assigning to a "new" object
        return cls

único

#Singleton Pattern
class Singleton:

    __single = None 

    def __init__(self):
        if not Singleton.__single:
            #Your definitions here
            self.x = 1 
        else:
            raise RuntimeError('A Singleton already exists') 

    @classmethod
    def getInstance(cls):
        if not cls.__single:
            cls.__single = Singleton()
        return cls.__single

Borg

#Borg Pattern
class Borg:

    __monostate = None

    def __init__(self):
        if not Borg.__monostate:
            Borg.__monostate = self.__dict__
            #Your definitions here
            self.x = 1

        else:
            self.__dict__ = Borg.__monostate

Prueba

#SINGLETON
print "\nSINGLETON\n"
A = Singleton.getInstance()
B = Singleton.getInstance()

print "At first B.x = {} and A.x = {}".format(B.x,A.x)
A.x = 2
print "After A.x = 2"
print "Now both B.x = {} and A.x = {}\n".format(B.x,A.x)
print  "Are A and B the same object? Answer: {}".format(id(A)==id(B))


#BORG
print "\nBORG\n"
A = Borg()
B = Borg()

print "At first B.x = {} and A.x = {}".format(B.x,A.x)
A.x = 2
print "After A.x = 2"
print "Now both B.x = {} and A.x = {}\n".format(B.x,A.x)
print  "Are A and B the same object? Answer: {}".format(id(A)==id(B))


#UNIQUE
print "\nUNIQUE\n"
A = Unique.init()
B = Unique.init()

print "At first B.x = {} and A.x = {}".format(B.x,A.x)
A.x = 2
print "After A.x = 2"
print "Now both B.x = {} and A.x = {}\n".format(B.x,A.x)
print  "Are A and B the same object? Answer: {}".format(id(A)==id(B))

Salida:

ÚNICO

At first B.x = 1 and A.x = 1
After A.x = 2
Now both B.x = 2 and A.x = 2

Are A and B the same object? Answer: True

BORG

At first B.x = 1 and A.x = 1
After A.x = 2
Now both B.x = 2 and A.x = 2

Are A and B the same object? Answer: False

UNIQUE

At first B.x = 1 and A.x = 1
After A.x = 2
Now both B.x = 2 and A.x = 2

Are A and B the same object? Answer: True

En mi opinión, la implementación de Unique es la más fácil, luego Borg y finalmente Singleton con una fea cantidad de dos funciones necesarias para su definición.

Cristian garcia
fuente
14

No lo es. Lo que generalmente no se recomienda es un patrón como este en Python:

class Singleton(object):

 _instance = None

 def __init__(self, ...):
  ...

 @classmethod
 def instance(cls):
  if cls._instance is None:
   cls._instance = cls(...)
  return cls._instance

donde usa un método de clase para obtener la instancia en lugar del constructor. La metaprogramación de Python permite métodos mucho mejores, por ejemplo, el de Wikipedia :

class Singleton(type):
    def __init__(cls, name, bases, dict):
        super(Singleton, cls).__init__(name, bases, dict)
        cls.instance = None

    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__call__(*args, **kw)

        return cls.instance

class MyClass(object):
    __metaclass__ = Singleton

print MyClass()
print MyClass()
bayer
fuente
+1 El patrón Monostate (Borg) es peor que Singleton (sí, es posible) porque private a = new Borg (); privado b = nuevo Borg (); b.mutate (); y ha cambiado! ¿Qué tan confuso es eso?
Michael Deardeuff
5
¿Mejor / peor? Eso dependería de su caso de uso, ¿no? Puedo pensar en varios casos en los que desearía que el estado se conservara de esa manera.
RickyA
5
Esto no es un problema, @MichaelDeardeuff. Este es el comportamiento previsto . Deberían ser iguales. Un problema en mi humilde opinión en el patrón borg es que si agrega algunas variables de inicialización en el método Borg .__ init__, como self.text = "", luego cambie ese objeto como borg1.text = "blah"y luego cree una instancia de un nuevo objeto `borg2 = Borg ()" - ¡zas! Todos los atributos borg1 que se inicializan en init . whiped están tan instanciar es imposible - o mejor: miembros en el patrón de Borg, no podrá inicializar los atributos en el init método!
nerdoc
Esto es posible en un Singleton porque hay una ifverificación de si ya existe una instancia, y si es así, ¡simplemente se devuelve SIN una inicialización primordial!
nerdoc
Lo mismo se puede (y debe hacerse) en Borg init . if __monostate: returny después de eso haz tu self.foo = 'bar'
volodymyr
8

Una clase básicamente describe cómo puede acceder (leer / escribir) el estado interno de su objeto.

En el patrón singleton solo puede tener una única clase, es decir, todos sus objetos le darán los mismos puntos de acceso al estado compartido. Esto significa que si tiene que proporcionar una API extendida, deberá escribir un contenedor, envolviendo el singleton

En el patrón borg puede extender la clase base "borg" y, por lo tanto, extender más convenientemente la API para su gusto.

Zed
fuente
8

Solo es mejor en esos pocos casos en los que realmente hay una diferencia. Como cuando subclases. El patrón Borg es extremadamente inusual, nunca lo he necesitado de verdad en diez años de programación en Python.

Lennart Regebro
fuente
2

Además, el patrón similar a Borg permite a los usuarios de la clase elegir si desean compartir el estado o crear una instancia separada. (si esto puede ser una buena idea o no es un tema aparte)

class MayBeBorg:
    __monostate = None

    def __init__(self, shared_state=True, ..):
        if shared_state:

            if not MayBeBorg.__monostate:
                MayBeBorg.__monostate = self.__dict__
            else:
                self.__dict__ = MayBeBorg.__monostate
                return
        self.wings = ..
        self.beak = ..
volodymyr
fuente