Warum ist das Borg-Muster in Python besser als das Singleton-Muster?

80

Warum ist das Borg-Muster besser als das Singleton-Muster ?

Ich frage, weil ich nicht sehe, dass sie zu etwas anderem führen.

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()

Singleton:

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()

Was ich hier anzeigen möchte, ist, dass das Serviceobjekt, ob als Borg oder Singleton implementiert, einen nicht trivialen internen Status hat (es bietet einen darauf basierenden Service) (ich meine, es muss etwas Nützliches sein, es ist kein Singleton / Borg nur für Spaß).

Und dieser Zustand muss eingeleitet werden. Hier ist die Singleton-Implementierung einfacher, da wir init als Aufbau des globalen Zustands behandeln. Ich finde es umständlich, dass das Borg-Objekt seinen internen Status abfragen muss, um zu sehen, ob es sich selbst aktualisieren soll.

Es wird schlimmer, je mehr innerer Zustand Sie haben. Wenn das Objekt beispielsweise das Teardown-Signal der Anwendung abhören muss, um sein Register auf der Festplatte zu speichern, sollte diese Registrierung ebenfalls nur einmal durchgeführt werden. Dies ist mit einem Singleton einfacher.

u0b34a0f6ae
quelle
1
Borg-Muster? ^ _ ^ Ich hatte zum ersten Mal davon gehört als c2.com/cgi/wiki?MonostatePattern
Jeffrey Hantin
9
Monostat? Wir sind die Martellis. Wir sagen Borg.
u0b34a0f6ae

Antworten:

64

Der wahre Grund, warum Borg anders ist, liegt in der Unterklasse.

Wenn Sie eine Borg unterordnen, haben die Objekte der Unterklasse denselben Status wie die Objekte der übergeordneten Klassen, es sei denn, Sie überschreiben explizit den gemeinsam genutzten Status in dieser Unterklasse. Jede Unterklasse des Singleton-Musters hat ihren eigenen Status und erzeugt daher unterschiedliche Objekte.

Auch im Singleton-Muster sind die Objekte tatsächlich gleich, nicht nur der Zustand (obwohl der Zustand das einzige ist, was wirklich wichtig ist).

David Raznick
quelle
1
> Auch im Singleton-Muster sind die Objekte tatsächlich dieselben, nicht nur der Status (obwohl der Status das einzige ist, was wirklich wichtig ist). Warum ist das eine schlechte Sache?
Agiliq
gute frage uswaretech, es ist ein teil meiner frage oben. Was ist das als schlecht gesagt?
u0b34a0f6ae
2
Ich habe nicht gesagt, dass es eine schlechte Sache ist. Es war eine meinungslose Beobachtung der Unterschiede. Entschuldigung für die Verwirrung. Manchmal ist der Singleton besser infakt, wenn Sie beispielsweise die Objekte ID für ID (obj) überprüfen, obwohl dies selten vorkommt.
David Raznick
Wie ist dann Nonein Python ein Singleton und kein Beispiel für ein Borg-Muster?
Chang Zhao
@ChangZhao: weil wir im Fall von None die gleiche Identität haben müssen, nicht nur den Status teilen. Wir machen x is NoneÜberprüfungen. Darüber hinaus ist None ein Sonderfall, da wir keine Unterklassen von None erstellen können.
Olivecoder
22

Wenn Sie in Python ein eindeutiges "Objekt" möchten, auf das Sie von überall aus zugreifen können, erstellen Sie einfach eine Klasse Unique, die nur statische Attribute, @staticmethods und @classmethods enthält. man könnte es das einzigartige Muster nennen. Hier implementiere und vergleiche ich die 3 Muster:

Einzigartig

#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

Singleton

#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

Prüfung

#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))

Ausgabe:

SINGLETON

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

Meiner Meinung nach ist die eindeutige Implementierung am einfachsten, dann Borg und schließlich Singleton mit einer hässlichen Anzahl von zwei Funktionen, die für die Definition benötigt werden.

Cristian Garcia
quelle
14

Es ist nicht. Was im Allgemeinen nicht empfohlen wird, ist ein Muster wie dieses in Python:

class Singleton(object):

 _instance = None

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

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

Dabei verwenden Sie eine Klassenmethode, um die Instanz anstelle des Konstruktors abzurufen. Die Metaprogrammierung von Python ermöglicht viel bessere Methoden, z. B. die auf 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
quelle
+1 Das Monostate (Borg) Muster ist schlechter als Singleton (ja, es ist möglich), weil privat a = new Borg (); privat b = neu Borg (); b.mutate (); und a wird geändert! Wie verwirrend ist das?
Michael Deardeuff
5
Am besten / schlechter? Das würde von Ihrem Anwendungsfall abhängen, nicht wahr? Ich kann mir eine Reihe von Fällen vorstellen, in denen Sie möchten, dass der Zustand so erhalten bleibt.
RickyA
5
Dies ist kein Problem, @MichaelDeardeuff. Dies ist beabsichtigtes Verhalten . Sie sollten gleich sein. Ein Problem IMHO im Borg-Muster ist, dass, wenn Sie einige Initialisierungsvariablen in der Borg .__ init__ -Methode hinzufügen self.text = "", dann ändern Sie dieses Objekt wie borg1.text = "blah"und instanziieren Sie dann ein neues Objekt `borg2 = Borg ()" - wham! Alle borg1-Attribute, die werden in init initialisiert werden ausgepeitscht, so dass eine Instanziierung unmöglich ist - oder besser: Im Borg-Muster dürfen Sie Mitgliedsattribute in der init- Methode NICHT initialisieren !
Nerdoc
Dies ist in einem Singleton möglich, da ifüberprüft wird, ob bereits eine Instanz vorhanden ist, und wenn ja, wird sie nur OHNE überschreibende Initialisierung zurückgegeben!
Nerdoc
Gleiches kann (und sollte) in Borg init erfolgen . if __monostate: returnund danach mach dein self.foo = 'bar'
volodymyr
8

Eine Klasse beschreibt im Wesentlichen, wie Sie auf den internen Status Ihres Objekts zugreifen (lesen / schreiben) können.

Im Singleton-Muster können Sie nur eine einzige Klasse haben, dh alle Ihre Objekte geben Ihnen dieselben Zugriffspunkte auf den gemeinsam genutzten Status. Dies bedeutet, dass Sie, wenn Sie eine erweiterte API bereitstellen müssen, einen Wrapper schreiben müssen, der den Singleton umschließt

Im Borg-Muster können Sie die Basisklasse "Borg" erweitern und dadurch die API bequemer nach Ihrem Geschmack erweitern.

Zed
quelle
8

Es ist nur in den wenigen Fällen besser, in denen Sie tatsächlich einen Unterschied haben. Wie wenn Sie Unterklasse. Das Borg-Muster ist äußerst ungewöhnlich, ich habe es in zehn Jahren Python-Programmierung nie wirklich gebraucht.

Lennart Regebro
quelle
2

Außerdem können Benutzer der Klasse anhand eines Borg-ähnlichen Musters auswählen, ob sie den Status freigeben oder eine separate Instanz erstellen möchten. (Ob dies eine gute Idee ist oder nicht, ist ein separates Thema)

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
quelle