Singletons in 4D

4D 20 R5 bietet eine leistungsstarke Funktion für Entwickler: Singletons!

Das Singleton Design Pattern erzeugt eine einzige Instanz einer Klasse, die in der gesamten Anwendung zugänglich ist.

Dieses Muster bietet viele Vorteile, darunter:

  • Host für prozessübergreifende Werte,
  • Hilfsklassen,
  • eine Basis für das Factory Design Pattern,
  • und vieles mehr.

Lesen Sie weiter, um mehr über dieses neue Konzept zu erfahren!

4D kennt 2 Arten von Singletons: Singletons, die pro Prozess eindeutig sind und Shared Singletons, die in der gesamten Anwendung eindeutig sind. Singletons sind Standardobjekte, während Shared Singletons gemeinsam genutzte Objekte sind.

Die Definition eines Singletons ist sehr einfach: Sie müssen lediglich das Schlüsselwort singleton in einen Klassenkonstruktor einfügen:

singleton Class constructor()

Die Definition eines gemeinsam genutzten Singletons ist ebenfalls sehr einfach: Sie müssen nur das Schlüsselwort singleton in einer gemeinsam genutzten Klasse verwenden:

shared singleton Class constructor()

Von diesem Moment an können Sie über das me-Attribut einfach auf Ihre Singletons zugreifen:

$singleton:=cs.SingletonClass.me

Das me-Attribut ist die Singleton-Instanz (auf die in anderen Sprachen oft über die Funktion getInstance() zugegriffen wird). Es wird instanziiert, wenn Sie das erste Mal auf Ihr Singleton zugreifen.

Beispiele

Singletons sind sehr nützlich, und wir erwarten, dass Sie sie häufig verwenden werden. Ich denke, Sie werden Beispiele für Singletons einer langen Beschreibung vorziehen, deshalb habe ich ein paar vorbereitet.

Hilfsklasse

Beginnen wir mit einem einfachen Beispiel: der Utility-Klasse. Die Utility-Klasse beherbergt viele Utility-Funktionen, die überall in Ihrer Anwendung verwendet werden. Wenn Sie diese Funktionen in einer einzigen Klasse zusammenfassen, wird Ihr Code übersichtlicher, da Sie alle Ihre Utility-Funktionen leicht finden können und sie Ihre Methoden nicht überladen.

//Class UtilityClass
 
singleton Class constructor()
 
Function utilityFunction1()
 
Function utilityFunction2()
 
...

Von diesem Moment an können Sie Ihre Dienstprogramme mit dieser Zeile aufrufen:

cs.UtilityClass.me.utilityFunction1()

Websocket-Server-Wrapper

Eine weitere klassische Verwendung von Singletons ist die Erstellung von Wrappern für Objekte, die überall in Ihrer Anwendung zugänglich sein sollen, wie z. B. ein Websocket-Server. Betrachten wir diese Klasse:

//WebSocketServerWrapper
 
singleton Class constructor()
 
$handler:=cs.myWebsocketServerHandler.new()
This.webSocketServer:=4D.WebSocketServer.new($handler; {dataType: “object”})
 
Function terminate()
 
This.webSocketServer.terminate()

Von diesem Moment an können Sie Ihren Webserver durch einen Aufruf starten:

CALL WORKER(“WebSocketServerWorker”; Formula(cs.WebSocketServerWrapper.me))

Und beenden Sie ihn durch den Aufruf von:

CALL WORKER(“WebSocketServerWorker”; Formula(cs.WebSocketServerWrapper.me.terminate()))

Factory

Im dritten Beispiel wird ein Entwurfsmuster implementiert: die Factory. Die Factory ist für die Erstellung neuer Instanzen von Objekten zuständig, in diesem Fall von Fahrzeugobjekten, die von einer VehicleFactory stammen:

//Class VehicleFactory
 
property vehicleBuilt:=0 //Number of vehicles built by the factory
 
shared singleton Class constructor()
 
shared Function buildVehicle($type : Text)->$vehicle : cs.Vehicle
 
  Case of
    : $type="car"
      $vehicle:=cs.Car.new()
    : $type="truck"
      $vehicle:=cs.Truck.new()
    : $type="sport car"
      $vehicle:=cs.SportCar.new()
    : $type="motorbike"
      $vehicle:=cs.Motorbike.new()
  Else
    $vehicle:=cs.Car.new()
  End case
 
  This.vehicleBuilt+=1

Da es sich um ein Singleton handelt, können Sie diese VehicleFactory aufrufen, um ein neues Fahrzeug von überall in Ihrem Prozess (oder Ihrer Anwendung, wenn Sie sie gemeinsam nutzen) mit einer einzigen Zeile zu erhalten:

$vehicle:=cs.VehicleFactory.me.buildVehicle("truck")

In diesem Fall tut diese Fabrik nicht viel, außer die Anzahl der gebauten Fahrzeuge zu zählen, bevor sie Ihr neues Fahrzeug zurückgibt, aber ich denke, Sie verstehen die Idee.

Wenn Sie möchten, dass die VehicleFactory in Ihrer gesamten Anwendung eindeutig ist, müssen Sie das Schlüsselwort shared hinzufügen. Die Funktion buildVehicle verändert die VehicleFactory (indem sie This.vehicleBuilt inkrementiert), daher müssen Sie ihr ebenfalls das Schlüsselwort shared hinzufügen, damit use und end use automatisch beim Aufruf und Beenden der Funktion aufgerufen werden.

Werte zwischen Prozessen

Eine weitere klassische Verwendung von Singletons ist das Hosten von prozessübergreifenden Werten, die Sie in Ihrer gesamten Anwendung verwenden werden. Hier ist ein Beispiel für ein solches Singleton:

//Class InterprocessSingleton
 
shared singleton Class constructor()
 
This.version:=2
This.buildNumber:=1056

Von dort aus können Sie z. B. sehr einfach auf Ihre globalen Variablen zugreifen:

cs.InterprocessSingleton.me.version

Tipp: Manchmal möchten Sie Ihre Interprozess-Werte beim Start Ihrer Anwendung initialisieren, typischerweise in der Methode On Application Startup, und in einem solchen Fall müssen Sie dem Singleton-Konstruktor Parameter übergeben, wie im folgenden Beispiel:

//Class InterprocessSingleton
 
shared singleton Class constructor($file : 4D.File)
 
$json:=$file.getText()
$settingsJson:=JSON Parse($json)
 
This.version:=$settingsJson.version
This.buildNumber:=$settingsJson.buildNumber

In diesem Fall stammen die Werte von version und buildNumber aus einer JSON-Datei. Im Falle von Singletons gibt es eine Möglichkeit, den Konstruktor mit Parametern aufzurufen, indem man die new()-Funktion aufruft. Zum Beispiel in unserem Fall:

$myFile:=File("/RESOURCES/settings.json")
cs.InterprocessSingleton.new($myFile)

Sie müssen darauf achten, dass der Aufruf von new() das erste Mal ist, dass auf das Singleton zugegriffen wird. Ein Aufruf von new(), nachdem auf das Singleton zugegriffen wurde, ruft den Klassenkonstruktor nicht mehr auf.

Besonderes Dankeschön

Ein besonderer Dank geht an Chris Belanger, der vorgeschlagen hat, das me-Attribut für den Zugriff auf ein Singleton hinzuzufügen.

Ich hoffe, Singletons sind für Sie genauso spannend wie für uns. Wenn Sie Fragen dazu haben, können Sie diese gerne im 4D Forum stellen.

Nicolas Brachfogel
Product Owner & Senior Developer - Nicolas Brachfogel kam 2017 als Senior Developer (4D Server und Netzwerke) zu 4D. Als Product Owner, der die Freigabe von Apple Silicon verwaltet, ist er für das Schreiben von User Stories und deren Umsetzung in funktionale Spezifikationen zuständig und stellt sicher, dass die Implementierungen der Funktionen den Kundenanforderungen entsprechen. Nicolas ist Absolvent des Institut Supérieur d'Informatique Appliquée (INSIA) und begann seine Karriere als Softwareentwickler im Jahr 2001. Nachdem er mehrere Jahre in Java und C++ programmiert hatte, spezialisierte er sich auf die Client-Server-Entwicklung für Videospielunternehmen. Als Server-Entwickler/Architekt arbeitete er erfolgreich an den Server-Architekturen vieler Spiele (Dofus Arena, Drakerz, Trivial Pursuit Go!).