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.