4D 20 R5 delivers a powerful feature for developers: Singletons!
The singleton design pattern creates a single instance of a class accessible throughout your application.
This pattern offers many benefits, including:
- host for interprocess values,
- utility classes,
- a base for the factory design pattern,
- and many more.
Keep reading for more information about this new concept!
4D has 2 types of singletons: singletons, which are unique per process and shared singletons, which are unique across the application. Singletons are standard objects, while shared singletons are shared objects.
Defining a singleton is extremely easy; you just need to add the singleton keyword to a class constructor:
singleton Class constructor()
Defining a shared singleton is also very easy; you need to use the singleton keyword on a shared class:
shared singleton Class constructor()
From that moment on, you can easily access your singletons through the me attribute:
$singleton:=cs.SingletonClass.me
The me attribute is the singleton instance (often accessed through the getInstance() function in other languages). It is instantiated the first time you access your singleton.
Examples
Singletons are very useful, and we expect you to start using them a lot. I think you’d prefer examples of singletons rather than a long description, so I’ve prepared a few.
Utility class
Let’s start with a simple example: the utility class. The utility class hosts many utility functions that are used everywhere in your application. Grouping these functions into a single class helps maintain your code, as it’s very easy to find all your utility functions, and they don’t clutter your methods.
//Class UtilityClass
singleton Class constructor()
Function utilityFunction1()
Function utilityFunction2()
...
From that moment on, you can call your utility functions with this line:
cs.UtilityClass.me.utilityFunction1()
Websocket server wrapper
Another classic use of singletons is to create wrappers for objects that should be accessible everywhere in your application, such as a websocket server. Let’s consider this class:
//WebSocketServerWrapper
singleton Class constructor()
$handler:=cs.myWebsocketServerHandler.new()
This.webSocketServer:=4D.WebSocketServer.new($handler; {dataType: “object”})
Function terminate()
This.webSocketServer.terminate()
From that moment on, you can start your Webserver by calling:
CALL WORKER(“WebSocketServerWorker”; Formula(cs.WebSocketServerWrapper.me))
And terminate it by calling:
CALL WORKER(“WebSocketServerWorker”; Formula(cs.WebSocketServerWrapper.me.terminate()))
Factory
The third example implements a design pattern: the factory. The factory is in charge of creating new instances of objects, in that case, Vehicle objects coming from a 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
Thanks to being a Singleton, you can call this VehicleFactory to get a new Vehicle from everywhere in your application with a single line:
$vehicle:=cs.VehicleFactory.me.buildVehicle("truck")
In that case, this factory doesn’t do much besides counting the number of vehicles built before returning your new vehicle, but I think you get the idea.
The function buildVehicle modifies the VehicleFactory (by incrementing This.vehicleBuilt), so you also need to add the shared keyword to it so use and end use will be called automatically when calling and exiting the function.
Interprocess values
Another classic use of singletons is to host interprocess values that you’ll use all across your application. Here’s an example of such a singleton:
//Class InterprocessSingleton
shared singleton Class constructor()
This.version:=2
This.buildNumber:=1056
From there, you can access your global variables very easily, for example:
cs.InterprocessSingleton.me.version
Tip: Sometimes, you want to initialize your interprocess values at your application startup, typically inside the On Application Startup method, and in such case, you may need to pass parameters to the singleton constructor like in the example below:
//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 that case, the values of version and buildNumber come from a JSON file. In the case of singletons, there’s a way to call the constructor with parameters by calling the new() function. For example, in our case:
$myFile:=File("/RESOURCES/settings.json")
cs.InterprocessSingleton.new($myFile)
You must be careful that the call to new() is the first time the singleton has been accessed. Calling new() after the singleton has been accessed won’t call the class constructor anymore.
Special thanks
I want to give a special thank you to Chris Belanger, who suggested adding the me attribute to access a singleton.
I hope singletons are as exciting for you as they are for us. If you have any questions about them, feel free to ask them on the 4D forum.