Singletons dans 4D

4D 20 R5 offre une fonctionnalité puissante aux développeurs : Les singletons !

Le design pattern singleton consiste à créer une instance unique d’une classe accessible dans l’ensemble de l’application.

Ce modèle offre de nombreux avantages, permettant de créer :

  • un wrapper pour les variables interprocess,
  • des classes utilitaires,
  • une base pour le design pattern de la fabrique,
  • et bien d’autres encore.

Poursuivez votre lecture pour en savoir plus sur cette nouveauté !

4D a 2 types de singletons : les singletons, qui sont uniques par processus et les singletons partagés, qui sont uniques dans toute l’application. Les singletons sont des objets standards, tandis que les singletons partagés sont des objets partagés.

Définir un singleton est extrêmement simple; il suffit d’ajouter le mot-clé singleton au constructeur d’une classe :

singleton Class constructor()

La définition d’un singleton partagé est également très simple; il suffit d’utiliser le mot-clé singleton dans une classe partagée :

shared singleton Class constructor()

Dès lors, vous pouvez facilement accéder à vos singletons grâce à l’attribut me :

$singleton:=cs.SingletonClass.me

L’attribut me est l’instance du singleton (souvent accessible via la fonction getInstance() dans d’autres langages). Il est instancié la première fois que vous accédez à votre singleton.

Exemples de singleton

Les singletons sont très utiles, et nous nous attendons à ce que vous les utilisiez beaucoup. Je pense que vous préférerez des exemples de singletons plutôt qu’une longue description, c’est pourquoi j’en ai préparé quelques-uns pour vous.

Classe utilitaire

Commençons par un exemple simple : la classe utilitaire. La classe utilitaire héberge de nombreuses fonctions utilitaires qui sont utilisées partout dans votre application. Le fait de regrouper ces fonctions dans une seule classe facilite la maintenance de votre code, car il est très facile de retrouver vos fonctions utilitaires, et elles n’encombrent pas vos méthodes.

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

À partir de ce moment, vous pouvez appeler vos fonctions utilitaires avec cette ligne :

cs.UtilityClass.me.utilityFunction1()

Wrapper pour Websocket server 

Une autre utilisation classique des singletons est de créer des wrappers pour des objets qui doivent être accessibles partout dans votre application, comme un websocket server. Considérons cette classe :

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

À partir de ce moment, vous pouvez démarrer votre serveur en appelant :

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

Et le terminer en appelant :

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

Design pattern : la fabrique

Le troisième exemple met en œuvre un design pattern : la fabrique. La fabrique est chargée de créer de nouvelles instances d’objets, dans ce cas, des objets Vehicle provenant d’un VehicleFactory :

//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

Grâce à son statut de Singleton, vous pouvez appeler cette VehicleFactory pour obtenir un nouveau véhicule depuis n’importe quel endroit de votre application en une seule ligne :

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

Dans ce cas, cette fabrique ne fait pas grand chose à part compter le nombre de véhicules construits avant de renvoyer votre nouveau véhicule, mais je pense que vous avez compris l’idée.

La fonction buildVehicle modifie la VehicleFactory (en incrémentant This.vehicleBuilt), vous devez donc également lui ajouter le mot-clé shared pour que use et end use soient appelés automatiquement lors de l’appel et de la sortie de la fonction.

Variables interprocess

Une autre utilisation classique des singletons consiste à héberger des variables interprocess que vous utiliserez dans l’ensemble de votre application. Voici un exemple d’un tel singleton :

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

À partir de là, vous pouvez accéder très facilement à vos variables globales, par exemple :

cs.InterprocessSingleton.me.version

Astuce : Parfois, vous souhaitez initialiser vos variables interprocess au démarrage de votre application, typiquement dans la méthode On Application Startup, et dans ce cas, vous pouvez avoir besoin de passer des paramètres au constructeur du singleton comme dans l’exemple ci-dessous :

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

Dans ce cas, les valeurs de version et buildNumber proviennent d’un fichier JSON. Dans le cas des singletons, il existe un moyen d’appeler le constructeur avec des paramètres en appelant la fonction new(). Par exemple, dans notre cas :

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

Il faut faire attention à ce que l’appel à new() soit le premier accès au singleton. Appeler new() après que le singleton ait été instancié n’appellera plus le constructeur de la classe.

Remerciements

Je tiens à remercier tout particulièrement Chris Belanger, qui a suggéré d’ajouter l’attribut me pour accéder à un singleton.

J’espère que les singletons sont aussi passionnants pour vous qu’ils le sont pour nous. Si vous avez des questions à ce sujet, n’hésitez pas à les poser sur le forum 4D.

Nicolas Brachfogel
- Product Owner & Senior Developer - Nicolas Brachfogel a rejoint 4D en 2017 en tant que développeur senior (4D Server et networking) et en tant que Product Owner pour gérer la mise en production d'Apple Silicon. Il est chargé de rédiger les user stories et de les traduire en spécifications fonctionnelles, ainsi que de s'assurer que les implémentations des fonctionnalités répondent aux besoins des clients. Diplômé de l'Institut Supérieur d'Informatique Appliquée (INSIA), Nicolas a commencé sa carrière en tant que développeur de logiciels en 2001. Après plusieurs années de programmation en Java et C++, il s'est spécialisé dans le développement client-serveur pour des sociétés de jeux vidéo. En tant que développeur/architecte serveur, il a travaillé avec succès sur les architectures serveur de nombreux jeux (Dofus Arena, Drakerz, Trivial Pursuit Go !).