4D 20 R5 offre una potente funzionalità agli sviluppatori: I singleton!
Il design pattern singleton crea una singola istanza di una classe accessibile in tutta l’applicazione.
Questo pattern offre molti vantaggi, tra cui:
- host per valori interprocesso,
- classi di utilità,
- una base per il design pattern factory,
- e molti altri.
Continuate a leggere per maggiori informazioni su questo nuovo concetto!
4D ha due tipi di singleton: i singleton, che sono unici per processo, e i singleton condivisi, che sono unici per tutta l’applicazione. I singleton sono oggetti standard, mentre i singleton condivisi sono oggetti condivisi.
Definire un singleton è estremamente semplice: basta aggiungere la parola chiave singleton al costruttore di una classe:
singleton Class constructor()
Anche la definizione di un singleton condiviso è molto semplice: basta usare la parola chiave singleton in una classe condivisa:
shared singleton Class constructor()
Da quel momento in poi, si può facilmente accedere ai singleton attraverso l’attributo me:
$singleton:=cs.SingletonClass.me
L’attributo me è l’istanza del singleton (spesso accessibile tramite la funzione getInstance() in altri linguaggi). Viene istanziato la prima volta che si accede al singleton.
Esempi
I singleton sono molto utili e ci aspettiamo che li usiate spesso. Penso che preferireste degli esempi di singleton piuttosto che una lunga descrizione, quindi ne ho preparati alcuni.
Classe di utilità
Cominciamo con un esempio semplice: la classe utility. La classe utility ospita molte funzioni di utilità che vengono utilizzate ovunque nella vostra applicazione. Raggruppare queste funzioni in un’unica classe aiuta a mantenere il codice, poiché è molto facile trovare tutte le funzioni di utilità e non ingombrano i metodi.
//Class UtilityClass
singleton Class constructor()
Function utilityFunction1()
Function utilityFunction2()
...
Da questo momento in poi, si possono chiamare le funzioni di utilità con questa riga:
cs.UtilityClass.me.utilityFunction1()
Websocket server wrapper
Un altro uso classico dei singleton è quello di creare wrapper per oggetti che devono essere accessibili ovunque nell’applicazione, come un server websocket. Consideriamo questa classe:
//WebSocketServerWrapper
singleton Class constructor()
$handler:=cs.myWebsocketServerHandler.new()
This.webSocketServer:=4D.WebSocketServer.new($handler; {dataType: “object”})
Function terminate()
This.webSocketServer.terminate()
Da quel momento in poi, è possibile avviare il server Web chiamando:
CALL WORKER(“WebSocketServerWorker”; Formula(cs.WebSocketServerWrapper.me))
E terminarlo chiamando:
CALL WORKER(“WebSocketServerWorker”; Formula(cs.WebSocketServerWrapper.me.terminate()))
Fabbrica
Il terzo esempio implementa un modello di progettazione: il factory. La fabbrica ha il compito di creare nuove istanze di oggetti, in questo caso oggetti Veicolo provenienti da una 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
Grazie al fatto di essere un singleton, è possibile chiamare questa VehicleFactory per ottenere un nuovo veicolo da qualsiasi punto del processo (o dell’applicazione, se la si rende condivisa) con una sola riga:
$vehicle:=cs.VehicleFactory.me.buildVehicle("truck")
In questo caso, questa fabbrica non fa molto oltre a contare il numero di veicoli costruiti prima di restituire il nuovo veicolo, ma credo che si capisca l’idea.
Se si vuole che la VehicleFactory sia unica per tutta l’applicazione, occorre aggiungere la parola chiave shared. La funzione buildVehicle modifica la VehicleFactory (incrementando This.vehicleBuilt), quindi è necessario aggiungere anche la parola chiave shared, in modo che use e end use vengano richiamati automaticamente quando si chiama e si esce dalla funzione.
Valori interprocesso
Un altro uso classico dei singleton è quello di ospitare valori interprocesso che verranno utilizzati in tutta l’applicazione. Ecco un esempio di singleton di questo tipo:
//Class InterprocessSingleton
shared singleton Class constructor()
This.version:=2
This.buildNumber:=1056
Da qui, ad esempio, è possibile accedere alle variabili globali in modo molto semplice:
cs.InterprocessSingleton.me.version
Suggerimento: a volte si desidera inizializzare i valori interprocesso all’avvio dell’applicazione, in genere all’interno del metodo On Application Startup; in questo caso, potrebbe essere necessario passare dei parametri al costruttore del singleton, come nell’esempio seguente:
//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 questo caso, i valori di version e buildNumber provengono da un file JSON. Nel caso dei singleton, c’è un modo per chiamare il costruttore con i parametri, richiamando la funzione new(). Ad esempio, nel nostro caso:
$myFile:=File("/RESOURCES/settings.json")
cs.InterprocessSingleton.new($myFile)
Bisogna fare attenzione che la chiamata a new() sia la prima volta che si accede al singleton. Chiamando new() dopo l’accesso al singleton non si chiamerà più il costruttore della classe.
Ringraziamenti speciali
Voglio fare un ringraziamento speciale a Chris Belanger, che ha suggerito di aggiungere l’attributo me per accedere a un singleton.
Spero che i singleton siano entusiasmanti per voi come lo sono per noi. Se avete domande in merito, non esitate a farle sul forum di 4D.