Singletons nach Fowler

Nachdem in letztens ja gegen Singletons gewettert habe, habe ich nun selbst mal eines implementiert. Wie im verlinkten Artikel erläutert, gibt es ein paar reale Einsatzszenarien, in denen sie wirklich hilfreich sind. Einen dieser Fälle hatte ich: Ich wollte eine prozedurale API (konkret ging es um MPI) kapseln.

Bei der Realisierung habe ich mich für Martin Fowlers Ansatz entschieden, da dieser, wie im angesprochenen Artikel erläutert, die Testbarkeit erhöht. Im Folgenden nochmal etwas ausführlicherer Pseudocode und eine Beschreibung zu diesem Ansatz:

Die Idee

Anstatt den Konstruktor privat oder sonst wie unbenutzbar zu machen und eine Klassenmethode getInstance() bereit zu stellen, gibt es eine Klassenmethode load(), die eine vorher erzeugte Singleton-Instanz entgegen nimmt. Es kann also durchaus passieren, dass mehrere Instanzen der Singleton-Klasse existieren. Das Muster stellt jedoch sicher, dass immer nur genau eine Instanz benutzbar sind.

Um das zu realisieren, sind alle Instanzmethoden protected und werden über öffentliche Klassenmethoden aufgerufen. Die protected Methoden sind virtuell, was es ermöglicht, sie in abgeleiteten Klassen zu überschreiben. Durch dynamische Bindung ist es dann möglich, das Singleton durch einen Stub zu ersetzen.

Um die Threadsicherheit zu gewährleisten, müssen alle (kritischen) Klassenmethoden, wie auch der Konstruktor synchronisiert sein. In Sprachen ohne GarbageCollector ist dann noch eine weitere Methode unload() nötig, die die geladene Instanz wieder freigibt.

Der (Pseudo-)Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Singleton
var
  private static soleInstance: Singleton;
  protected someValue: Integer; // nicht statisch!
begin

  protected virtual method doSthDyn() : Integer; // nicht statisch! Nötig um dynamische Bindung zu realisieren
  begin
    return self.someValue;
  end method;
 
  public synchronized constructor create(); // Konstructor muss public sein
  begin
    assert(soleInstance = null); // so vermeiden wir das angesprochene Problem des zweimaligen Ladens

    initSth();
  end constructor;
 
  public static synchronized method load(instance: Singleton);
  begin
    assert soleInstance = null; // so vermeiden wir das angesprochene Problem des zweimaligen Ladens

    soleInstace := instance;
  end method;
 
  public static synchronized method unload();
  begin
    soleInstance.free(); // Objekt freigeben
  end method;
 
  public static synchronized method doSth() : Integer;
  begin
    return soleInstance.doSthDyn(); // nötig, da statische Methoden auch statisch gebunden werden
  end method;
 
end;

Wie oben angedeutet sichert eine Assertion, dass es nicht zu dem im Singleton-Artikel beschriebenen Problem kommt. Das heißt, genau genommen tut sie das nicht. Das Problem besteht natürlich weiterhin, jedoch erhöht die Assertion die Wahrscheinlichkeit, dass, sollte man den Fehler gemacht haben, load() zwei Mal aufzurufen, das Problem möglichst schnell erkannt und behoben werden kann.

Auch im Konstruktor ist u.U. so eine Assertion nötig. Normalerweise macht es nichts, wenn eine weitere Instanz existiert, weil das Muster sicher stellt, dass nur maximal eine Instanz benutzt werden kann. Problematisch wird es aber dann, wenn der Konstruktor Code mit Seiteneffekten ausführt. In meinem Fall initialisiert er MPI und so eine Initialisierung sollte ja nur einmal gemacht werden. Deshalb ist auch hier eine Assertion nötig.

Benutzung

1
2
3
4
5
  Singeleton.load(new Singleton.create()); // eine neue Instanz als Parameter an load() übergeben
  ...
  Singleton.doSth(); // mit der geladenen Instanz arbeiten
  ...
  Singleton.unload(); // am Ende geladene Instanz wieder freigeben

Einen Stub erstellen

Folgendermaßen kann man einen Stub erstellen:

1
2
3
4
5
6
7
8
class Stub extends Singleton // Stub von Singleton ableiten
  override protected synchronized method doSthDyn() : Integer; // diese Methode wollen wir ersetzen
  begin
    // hier könnten wir eine Logausgabe machen oder was auch immer wir für sinnvoll halten
    // dummy-Wert zurück geben:
    return 42;  
  end method;
end;

Benutzung des Stub

1
2
3
4
5
  Singeleton.load(new Stub.create()); // eine neue Stub-Instanz als Parameter an load() übergeben
  ...
  Singleton.doSth(); // mit der geladenen Instanz arbeiten
  ...
  Singleton.unload(); // am Ende geladene Instanz wieder freigeben

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert